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

Issue:
Add properties for Manage LIFF APIs
Need adding properties for liff.
Now some props (description, features... ) do not exist.
https://developers.line.biz/en/reference/liff-server/#add-liff-app

https://github.com/line/line-bot-sdk-go/blob/6948d5d117bc0330cbb1fc199eb3890311918536/linebot/liff.go#L35-L50

Pull request description:
Add liffApp properties
- remote ViewRequest
- API change, add/update liff should call LIFFApp{} instead of View{}
- fixes #335

Patch:
diff --git a/linebot/liff.go b/linebot/liff.go
index b252bb6..7048d1d 100644
--- a/linebot/liff.go
+++ b/linebot/liff.go
@@ -32,21 +32,39 @@ const (
 	LIFFViewTypeFull    LIFFViewType = "full"
 )
 
+// LIFFViewScopeType type
+type LIFFViewScopeType string
+
+// LIFFViewScopeType constants
+const (
+	LIFFViewScopeTypeOpenID           LIFFViewScopeType = "openid"
+	LIFFViewScopeTypeEmail            LIFFViewScopeType = "email"
+	LIFFViewScopeTypeProfile          LIFFViewScopeType = "profile"
+	LIFFViewScopeTypeChatMessageWrite LIFFViewScopeType = "chat_message.write"
+)
+
 // LIFFApp type
 type LIFFApp struct {
-	LIFFID string `json:"liffId"`
-	View   View   `json:"view"`
-}
-
-// ViewRequest type
-type ViewRequest struct {
-	View View `json:"view"`
+	LIFFID               string              `json:"liffId"`
+	View                 View                `json:"view"`
+	Description          string              `json:"description,omitempty"`
+	Features             *LIFFAppFeatures    `json:"features,omitempty"`
+	PermanentLinkPattern string              `json:"permanentLinkPattern,omitempty"`
+	Scope                []LIFFViewScopeType `json:"scope,omitempty"`
+	BotPrompt            string              `json:"botprompt,omitempty"`
 }
 
 // View type
 type View struct {
-	Type LIFFViewType `json:"type"`
-	URL  string       `json:"url"`
+	Type       LIFFViewType `json:"type"`
+	URL        string       `json:"url"`
+	ModlueMode bool         `json:"moduleMode,omitempty"`
+}
+
+// LIFFAppFeatures type
+type LIFFAppFeatures struct {
+	BLE    bool `json:"ble,omitempty"`
+	QRCode bool `json:"qrCode,omitempty"`
 }
 
 // GetLIFF method
@@ -79,10 +97,10 @@ func (call *GetLIFFAllCall) Do() (*LIFFAppsResponse, error) {
 }
 
 // AddLIFF method
-func (client *Client) AddLIFF(view View) *AddLIFFCall {
+func (client *Client) AddLIFF(app LIFFApp) *AddLIFFCall {
 	return &AddLIFFCall{
-		c:    client,
-		view: view,
+		c:   client,
+		app: app,
 	}
 }
 
@@ -91,7 +109,7 @@ type AddLIFFCall struct {
 	c   *Client
 	ctx context.Context
 
-	view View
+	app LIFFApp
 }
 
 // WithContext method
@@ -103,9 +121,19 @@ func (call *AddLIFFCall) WithContext(ctx context.Context) *AddLIFFCall {
 func (call *AddLIFFCall) encodeJSON(w io.Writer) error {
 	enc := json.NewEncoder(w)
 	return enc.Encode(&struct {
-		View View `json:"view"`
+		View                 View                `json:"view"`
+		Description          string              `json:"description,omitempty"`
+		Features             *LIFFAppFeatures    `json:"features,omitempty"`
+		PermanentLinkPattern string              `json:"permanentLinkPattern,omitempty"`
+		Scope                []LIFFViewScopeType `json:"scope,omitempty"`
+		BotPrompt            string              `json:"botPrompt,omitempty"`
 	}{
-		View: call.view,
+		View:                 call.app.View,
+		Description:          call.app.Description,
+		Features:             call.app.Features,
+		PermanentLinkPattern: call.app.PermanentLinkPattern,
+		Scope:                call.app.Scope,
+		BotPrompt:            call.app.BotPrompt,
 	})
 }
 
@@ -124,11 +152,11 @@ func (call *AddLIFFCall) Do() (*LIFFIDResponse, error) {
 }
 
 // UpdateLIFF method
-func (client *Client) UpdateLIFF(liffID string, view View) *UpdateLIFFCall {
+func (client *Client) UpdateLIFF(liffID string, app LIFFApp) *UpdateLIFFCall {
 	return &UpdateLIFFCall{
 		c:      client,
 		liffID: liffID,
-		view:   view,
+		app:    app,
 	}
 }
 
@@ -138,7 +166,7 @@ type UpdateLIFFCall struct {
 	ctx context.Context
 
 	liffID string
-	view   View
+	app    LIFFApp
 }
 
 // WithContext method
@@ -150,11 +178,19 @@ func (call *UpdateLIFFCall) WithContext(ctx context.Context) *UpdateLIFFCall {
 func (call *UpdateLIFFCall) encodeJSON(w io.Writer) error {
 	enc := json.NewEncoder(w)
 	return enc.Encode(&struct {
-		Type LIFFViewType `json:"type"`
-		URL  string       `json:"url"`
+		View                 View                `json:"view"`
+		Description          string              `json:"description,omitempty"`
+		Features             *LIFFAppFeatures    `json:"features,omitempty"`
+		PermanentLinkPattern string              `json:"permanentLinkPattern,omitempty"`
+		Scope                []LIFFViewScopeType `json:"scope,omitempty"`
+		BotPrompt            string              `json:"botPrompt,omitempty"`
 	}{
-		Type: call.view.Type,
-		URL:  call.view.URL,
+		View:                 call.app.View,
+		Description:          call.app.Description,
+		Features:             call.app.Features,
+		PermanentLinkPattern: call.app.PermanentLinkPattern,
+		Scope:                call.app.Scope,
+		BotPrompt:            call.app.BotPrompt,
 	})
 }
 


Test patch:
diff --git a/linebot/liff_test.go b/linebot/liff_test.go
index 6316021..9e5f05f 100644
--- a/linebot/liff_test.go
+++ b/linebot/liff_test.go
@@ -60,6 +60,37 @@ func TestGetLIFF(t *testing.T) {
 				},
 			},
 		},
+		{
+			ResponseCode: 200,
+			Response:     []byte(`{"apps":[{"liffId":"{liffId}","view":{"type":"full","url":"https://example.com/myservice"},"description":"Happy New York","permanentLinkPattern":"concat"},{"liffId":"{liffId}","view":{"type":"tall","url":"https://example.com/myservice2"},"features":{"ble":true,"qrCode":true},"permanentLinkPattern":"concat","scope":["profile","chat_message.write"],"botPrompt":"none"}]}`),
+			Want: want{
+				RequestBody: []byte(``),
+				Response: &LIFFAppsResponse{
+					Apps: []LIFFApp{
+						{
+							LIFFID: "{liffId}",
+							View: View{
+								Type: LIFFViewTypeFull,
+								URL:  "https://example.com/myservice",
+							},
+							Description:          "Happy New York",
+							PermanentLinkPattern: "concat",
+						},
+						{
+							LIFFID: "{liffId}",
+							View: View{
+								Type: LIFFViewTypeTall,
+								URL:  "https://example.com/myservice2",
+							},
+							Features:             &LIFFAppFeatures{BLE: true, QRCode: true},
+							PermanentLinkPattern: "concat",
+							Scope:                []LIFFViewScopeType{LIFFViewScopeTypeProfile, LIFFViewScopeTypeChatMessageWrite},
+							BotPrompt:            "none",
+						},
+					},
+				},
+			},
+		},
 	}
 	var currentTestIdx int
 	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -97,6 +128,7 @@ func TestGetLIFF(t *testing.T) {
 		t.Fatal(err)
 	}
 	for i, tc := range testCases {
+		currentTestIdx = i
 		t.Run(strconv.Itoa(i), func(t *testing.T) {
 			res, err := client.GetLIFF().Do()
 			if err != nil {
@@ -116,15 +148,17 @@ func TestAddLIFF(t *testing.T) {
 		Error       error
 	}
 	testCases := []struct {
-		View         View
+		LIFFApp      LIFFApp
 		ResponseCode int
 		Response     []byte
 		Want         want
 	}{
 		{
-			View: View{
-				Type: LIFFViewTypeFull,
-				URL:  "https://example.com/myservice",
+			LIFFApp: LIFFApp{
+				View: View{
+					Type: LIFFViewTypeFull,
+					URL:  "https://example.com/myservice",
+				},
 			},
 			ResponseCode: 200,
 			Response:     []byte(`{"liffId":"testliffId"}`),
@@ -133,6 +167,25 @@ func TestAddLIFF(t *testing.T) {
 				Response:    &LIFFIDResponse{LIFFID: "testliffId"},
 			},
 		},
+		{
+			LIFFApp: LIFFApp{
+				View: View{
+					Type: LIFFViewTypeFull,
+					URL:  "https://example.com/myservice2",
+				},
+				Scope:                []LIFFViewScopeType{LIFFViewScopeTypeProfile, LIFFViewScopeTypeChatMessageWrite},
+				PermanentLinkPattern: "concat",
+				Description:          "Service Example",
+				Features:             &LIFFAppFeatures{BLE: true, QRCode: true},
+				BotPrompt:            "none",
+			},
+			ResponseCode: 200,
+			Response:     []byte(`{"liffId":"testliffId3"}`),
+			Want: want{
+				RequestBody: []byte(`{"view":{"type":"full","url":"https://example.com/myservice2"},"description":"Service Example","features":{"ble":true,"qrCode":true},"permanentLinkPattern":"concat","scope":["profile","chat_message.write"],"botPrompt":"none"}` + "\n"),
+				Response:    &LIFFIDResponse{LIFFID: "testliffId3"},
+			},
+		},
 	}
 	var currentTestIdx int
 	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -150,7 +203,7 @@ func TestAddLIFF(t *testing.T) {
 			t.Fatal(err)
 		}
 		if !reflect.DeepEqual(body, tc.Want.RequestBody) {
-			t.Errorf("RequestBody\n %s; want\n %s", body, tc.Want.RequestBody)
+			t.Errorf("%d, RequestBody\n %s; want\n %s", currentTestIdx, body, tc.Want.RequestBody)
 		}
 		w.WriteHeader(tc.ResponseCode)
 		w.Write(tc.Response)
@@ -170,8 +223,9 @@ func TestAddLIFF(t *testing.T) {
 		t.Fatal(err)
 	}
 	for i, tc := range testCases {
+		currentTestIdx = i
 		t.Run(strconv.Itoa(i), func(t *testing.T) {
-			res, err := client.AddLIFF(tc.View).Do()
+			res, err := client.AddLIFF(tc.LIFFApp).Do()
 			if err != nil {
 				t.Error(err)
 			}
@@ -190,21 +244,23 @@ func TestUpdateLIFF(t *testing.T) {
 	}
 	testCases := []struct {
 		LIFFID       string
-		View         View
+		LIFFApp      LIFFApp
 		ResponseCode int
 		Response     []byte
 		Want         want
 	}{
 		{
 			LIFFID: "testliffId",
-			View: View{
-				Type: LIFFViewTypeFull,
-				URL:  "https://example.com/myservice",
+			LIFFApp: LIFFApp{
+				View: View{
+					Type: LIFFViewTypeFull,
+					URL:  "https://example.com/myservice",
+				},
 			},
 			ResponseCode: 200,
 			Response:     []byte(``),
 			Want: want{
-				RequestBody: []byte(`{"type":"full","url":"https://example.com/myservice"}` + "\n"),
+				RequestBody: []byte(`{"view":{"type":"full","url":"https://example.com/myservice"}}` + "\n"),
 				Response:    &BasicResponse{},
 			},
 		},
@@ -246,7 +302,7 @@ func TestUpdateLIFF(t *testing.T) {
 	}
 	for i, tc := range testCases {
 		t.Run(strconv.Itoa(i), func(t *testing.T) {
-			res, err := client.UpdateLIFF(tc.LIFFID, tc.View).Do()
+			res, err := client.UpdateLIFF(tc.LIFFID, tc.LIFFApp).Do()
 			if err != nil {
 				t.Error(err)
 			}