diff --git a/api_message_send_ephemeral.go b/api_message_send_ephemeral.go index 3985e71c..c6848f98 100644 --- a/api_message_send_ephemeral.go +++ b/api_message_send_ephemeral.go @@ -19,6 +19,7 @@ package lark import ( "context" + "encoding/json" ) // SendEphemeralMessage 用于机器人在群会话中发送仅指定用户可见的消息卡片。卡片上将展示"仅对你可见"标识。 @@ -74,7 +75,8 @@ type SendEphemeralMessageReq struct { UserID string `json:"user_id,omitempty"` // 指定发送临时消息卡片的用户, 其他人将无法看到临时消息卡片;只需要填 open_id、email、user_id中的一个即可, 推荐使用 OpenID, 获取方式可参考文档[如何获取 Open ID?](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-obtain-openid) (服务端依次读取字段的顺序为 open_id > user_id > email) Email string `json:"email,omitempty"` // 指定发送临时消息卡片的用户, 其他人将无法看到临时消息卡片;只需要填 open_id、email、user_id中的一个即可, 推荐使用 OpenID, 获取方式可参考文档[如何获取 Open ID?](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-obtain-openid) (服务端依次读取字段的顺序为 open_id > user_id > email) MsgType MsgType `json:"msg_type,omitempty"` // 消息的类型, 此处固定填 "interactive" - Card *MessageContentCard `json:"card,omitempty"` // 消息卡片的描述内容, 具体参考 [基础结构](https://open.feishu.cn/document/ukTMukTMukTM/uEjNwUjLxYDM14SM2ATN) + Card *MessageContentCard `json:"-"` // JSON 1.0 消息卡片内容 + CardV2 json.RawMessage `json:"-"` // JSON 2.0 消息卡片内容 } // SendEphemeralMessageResp ... diff --git a/api_message_send_ephemeral_json.go b/api_message_send_ephemeral_json.go new file mode 100644 index 00000000..d04cde28 --- /dev/null +++ b/api_message_send_ephemeral_json.go @@ -0,0 +1,28 @@ +package lark + +import ( + "encoding/json" + "errors" +) + +func (r SendEphemeralMessageReq) MarshalJSON() ([]byte, error) { + if r.Card != nil && len(r.CardV2) > 0 { + return nil, errors.New("lark: SendEphemeralMessageReq Card and CardV2 cannot both be set") + } + + var card any + if len(r.CardV2) > 0 { + card = r.CardV2 + } else if r.Card != nil { + card = r.Card + } + + type request SendEphemeralMessageReq + return json.Marshal(struct { + request + Card any `json:"card,omitempty"` + }{ + request: request(r), + Card: card, + }) +} diff --git a/api_message_send_ephemeral_test.go b/api_message_send_ephemeral_test.go new file mode 100644 index 00000000..2220d526 --- /dev/null +++ b/api_message_send_ephemeral_test.go @@ -0,0 +1,80 @@ +package lark + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSendEphemeralMessageReqMarshalsV2Card(t *testing.T) { + card := json.RawMessage(`{"schema":"2.0","body":{"elements":[]}}`) + req := &SendEphemeralMessageReq{ + ChatID: "oc_test", + OpenID: "ou_test", + MsgType: MsgTypeInteractive, + CardV2: card, + } + + body, err := json.Marshal(req) + require.NoError(t, err) + require.JSONEq(t, `{ + "chat_id":"oc_test", + "open_id":"ou_test", + "msg_type":"interactive", + "card":{"schema":"2.0","body":{"elements":[]}} + }`, string(body)) +} + +func TestSendEphemeralMessageReqKeepsV1CardCompatibility(t *testing.T) { + card := &MessageContentCard{ + Modules: []MessageContentCardModule{ + MessageContentCardModuleDIV{ + Text: &MessageContentCardObjectText{Tag: "lark_md", Content: "hello"}, + }, + }, + } + req := &SendEphemeralMessageReq{ + ChatID: "oc_test", + OpenID: "ou_test", + MsgType: MsgTypeInteractive, + Card: card, + } + var typedCard *MessageContentCard = req.Card + require.Same(t, card, typedCard) + + body, err := json.Marshal(req) + require.NoError(t, err) + require.JSONEq(t, `{ + "chat_id":"oc_test", + "open_id":"ou_test", + "msg_type":"interactive", + "card":{"elements":[{"tag":"div","text":{"tag":"lark_md","content":"hello"}}]} + }`, string(body)) +} + +func TestSendEphemeralMessageReqRejectsBothCardVersions(t *testing.T) { + req := &SendEphemeralMessageReq{ + Card: &MessageContentCard{}, + CardV2: json.RawMessage(`{"schema":"2.0","body":{"elements":[]}}`), + } + + _, err := json.Marshal(req) + require.ErrorContains(t, err, "Card and CardV2 cannot both be set") +} + +func TestSendEphemeralMessageReqOmitsEmptyCard(t *testing.T) { + req := &SendEphemeralMessageReq{ + ChatID: "oc_test", + OpenID: "ou_test", + MsgType: MsgTypeInteractive, + } + + body, err := json.Marshal(req) + require.NoError(t, err) + require.JSONEq(t, `{ + "chat_id":"oc_test", + "open_id":"ou_test", + "msg_type":"interactive" + }`, string(body)) +} diff --git a/internal_test.go b/internal_test.go index 7fc65e7a..1164b811 100644 --- a/internal_test.go +++ b/internal_test.go @@ -42,8 +42,9 @@ func Test_Request(t *testing.T) { } resp, err := ins.parseRawHttpRequest(ctx, &RawRequestReq{ - Method: "get", - URL: "http://x.com/:id", + MethodOption: newMethodOption(nil), + Method: "get", + URL: "http://x.com/:id", Body: req{ ID: string("1234"), }, @@ -60,8 +61,9 @@ func Test_Request(t *testing.T) { ID int `path:"id"` } resp, err := ins.parseRawHttpRequest(ctx, &RawRequestReq{ - Method: "get", - URL: "http://x.com/:id", + MethodOption: newMethodOption(nil), + Method: "get", + URL: "http://x.com/:id", Body: req{ ID: 1234, }, @@ -79,8 +81,9 @@ func Test_Request(t *testing.T) { ID int `path:"id"` } resp, err := ins.parseRawHttpRequest(ctx, &RawRequestReq{ - Method: "get", - URL: "http://x.com/:type/:id", + MethodOption: newMethodOption(nil), + Method: "get", + URL: "http://x.com/:type/:id", Body: req{ Type: "x", ID: 1234, @@ -99,8 +102,9 @@ func Test_Request(t *testing.T) { ID int `path:"id"` } resp, err := ins.parseRawHttpRequest(ctx, &RawRequestReq{ - Method: "get", - URL: "http://x.com/:id", + MethodOption: newMethodOption(nil), + Method: "get", + URL: "http://x.com/:id", Body: req{ Type: "x", ID: 1234, @@ -120,8 +124,9 @@ func Test_Request(t *testing.T) { Name string `json:"name"` } resp, err := ins.parseRawHttpRequest(ctx, &RawRequestReq{ - Method: "get", - URL: "http://x.com/:id", + MethodOption: newMethodOption(nil), + Method: "get", + URL: "http://x.com/:id", Body: req{ Type: "x", ID: 1234, @@ -144,9 +149,10 @@ func Test_Request(t *testing.T) { Image io.Reader `json:"image,omitempty"` // 图片内容,**示例值**:二进流 } resp, err := ins.parseRawHttpRequest(ctx, &RawRequestReq{ - Method: "get", - URL: "http://x.com", - IsFile: true, + MethodOption: newMethodOption(nil), + Method: "get", + URL: "http://x.com", + IsFile: true, Body: req{ ImageType: ImageTypeMessage, Image: bytes.NewReader([]byte("hi")), @@ -167,9 +173,10 @@ func Test_Request(t *testing.T) { Types []string `query:"types"` } resp, err := ins.parseRawHttpRequest(ctx, &RawRequestReq{ - Method: "get", - URL: "http://x.com", - IsFile: true, + MethodOption: newMethodOption(nil), + Method: "get", + URL: "http://x.com", + IsFile: true, Body: req{ Types: []string{"a", "b"}, }, diff --git a/test/drive_file_test.go b/test/drive_file_test.go index 8a6987dc..5f17d2f7 100644 --- a/test/drive_file_test.go +++ b/test/drive_file_test.go @@ -63,7 +63,6 @@ func Test_DriveFile(t *testing.T) { { resp, _, err := AppAllPermission.Ins().Drive.DownloadDriveFile(ctx, &lark.DownloadDriveFileReq{ FileToken: fileToken, - Range: [2]int64{}, }) as.Nil(err) as.NotNil(resp)