diff --git a/CHANGELOG.md b/CHANGELOG.md index 437a6a5..0d88a76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v1.15.0 + +- feat(im): support card template (#75) + ## v1.14.1 - feat(api): UploadFile support uploads binary file @@ -8,7 +12,7 @@ - refactor(im): build reply and update message with standalone methods - feat(im): UpdateMessage supports text and post in addition to card -- feat(im): ReplyMessage suports reply in thread +- feat(im): ReplyMessage supports reply in thread ## v1.13.3 diff --git a/README.md b/README.md index f954530..37de82c 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ Content functions: | Text | `MsgText` | Append plain text | May build with `TextBuilder` | | Post | `MsgPost` | Append rich text | May build with `PostBuilder` | | Card | `MsgInteractive` | Append interactive card | May build with [`CardBuilder`](card/README.md) | +| Template | `MsgInteractive` | Append card template | May build with [`CardBuilder`](card/README.md) | | ShareChat | `MsgShareCard` | Append group share card | | | ShareUser | `MsgShareUser` | Append user share card | | | Image | `MsgImage` | Append image | Required to upload to Lark server in advance | @@ -257,6 +258,7 @@ r.POST("/", func(c *gin.Context) { We may also setup callback for card actions (e.g. button). The URL challenge part is the same. We may use `LarkCardHandler` to handle the actions: + ```go r.Use(middleware.LarkCardHandler()) r.POST("/callback", func(c *gin.Context) { diff --git a/README_zhCN.md b/README_zhCN.md index f448931..f524d9c 100644 --- a/README_zhCN.md +++ b/README_zhCN.md @@ -171,18 +171,19 @@ Bind 函数: 内容函数大多跟消息类型是强关联的,类型错误不会生效。内容函数: -| 函数 | 适用范围 | 作用 | 备注 | -| --------- | ---------------- | ---------------- | ------------------------------------------------------- | -| Text | `MsgText` | 添加文本内容 | 可使用 `TextBuilder` 构造 | -| Post | `MsgPost` | 添加富文本内容 | 可使用 `PostBuilder` 构造 | -| Card | `MsgInteractive` | 添加交互式卡片 | 非国际化卡片可使用 [`CardBuilder`](card/README_zhCN.md) | -| ShareChat | `MsgShareCard` | 添加分享群卡片 | | -| ShareUser | `MsgShareUser` | 添加分享用户卡片 | | -| Image | `MsgImage` | 添加图片 | 需要先上传到飞书服务器 | -| File | `MsgFile` | 添加文件 | 需要先上传到飞书服务器 | -| Audio | `MsgAudio` | 添加音频 | 需要先上传到飞书服务器 | -| Media | `MsgMedia` | 添加媒体 | 需要先上传到飞书服务器 | -| Sticker | `MsgSticker` | 添加表情 | 需要先上传到飞书服务器 | +| 函数 | 适用范围 | 作用 | 备注 | +| --------- | ---------------- | ---------------- | ------------------------------------------------ | +| Text | `MsgText` | 添加文本内容 | 可使用 `TextBuilder` 构造 | +| Post | `MsgPost` | 添加富文本内容 | 可使用 `PostBuilder` 构造 | +| Card | `MsgInteractive` | 添加交互式卡片 | 可使用 [`CardBuilder`](card/README_zhCN.md) 构造 | +| Template | `MsgInteractive` | 添加卡片模板 | 可使用 [`CardBuilder`](card/README_zhCN.md) 构造 | +| ShareChat | `MsgShareCard` | 添加分享群卡片 | | +| ShareUser | `MsgShareUser` | 添加分享用户卡片 | | +| Image | `MsgImage` | 添加图片 | 需要先上传到飞书服务器 | +| File | `MsgFile` | 添加文件 | 需要先上传到飞书服务器 | +| Audio | `MsgAudio` | 添加音频 | 需要先上传到飞书服务器 | +| Media | `MsgMedia` | 添加媒体 | 需要先上传到飞书服务器 | +| Sticker | `MsgSticker` | 添加表情 | 需要先上传到飞书服务器 | ### 异常处理 diff --git a/api_message_test.go b/api_message_test.go index 268df4c..d182f35 100644 --- a/api_message_test.go +++ b/api_message_test.go @@ -385,6 +385,20 @@ func TestI18NCard(t *testing.T) { } } +func TestTemplateCardMessage(t *testing.T) { + b := NewTemplateBuilder() + c := b.BindTemplate("AAqCYI07MQWh1", "1.0.0", map[string]interface{}{ + "name": "志田千陽", + }) + msgV4 := NewMsgBuffer(MsgInteractive) + omV4 := msgV4.BindEmail(testUserEmail).Template(c).Build() + resp, err := bot.PostMessage(omV4) + if assert.NoError(t, err) { + assert.Equal(t, 0, resp.Code) + assert.NotEmpty(t, resp.Data.MessageID) + } +} + func TestEphemeralMessage(t *testing.T) { b := NewCardBuilder() card := b.Card( diff --git a/message.go b/message.go index 53ce042..ab7eb91 100644 --- a/message.go +++ b/message.go @@ -52,6 +52,7 @@ type MessageContent struct { Media *MediaContent `json:"media,omitempty"` File *FileContent `json:"file,omitempty"` Sticker *StickerContent `json:"sticker,omitempty"` + Template *TemplateContent `json:"template,omitempty"` } // TextContent . @@ -97,3 +98,15 @@ type FileContent struct { type StickerContent struct { FileKey string `json:"file_key"` } + +// TemplateContent . +type TemplateContent struct { + Type string `json:"type"` + Data templateData `json:"data,omitempty"` +} + +type templateData struct { + TemplateID string `json:"template_id"` + TemplateVersionName string `json:"template_version_name,omitempty"` + TemplateVariable map[string]interface{} +} diff --git a/message_v2.go b/message_v2.go index c2f3ae6..36812bc 100644 --- a/message_v2.go +++ b/message_v2.go @@ -73,7 +73,11 @@ func buildContent(om OutcomingMessage) string { case MsgPost: b, err = json.Marshal(om.Content.Post) case MsgInteractive: - b, err = json.Marshal(om.Content.Card) + if om.Content.Card != nil { + b, err = json.Marshal(om.Content.Card) + } else if om.Content.Template != nil { + b, err = json.Marshal(om.Content.Template) + } case MsgAudio: b, err = json.Marshal(om.Content.Audio) case MsgMedia: diff --git a/msg_buf.go b/msg_buf.go index 4f5cb4e..3af6c29 100644 --- a/msg_buf.go +++ b/msg_buf.go @@ -225,6 +225,16 @@ func (m *MsgBuffer) Card(cardContent string) *MsgBuffer { return m } +// Template sets raw template content +func (m *MsgBuffer) Template(tempateContent *TemplateContent) *MsgBuffer { + if m.msgType != MsgInteractive { + m.err = m.typeError("Template", MsgInteractive) + return m + } + m.message.Content.Template = tempateContent + return m +} + // Build message and return message body func (m *MsgBuffer) Build() OutcomingMessage { return m.message diff --git a/msg_template_builder.go b/msg_template_builder.go new file mode 100644 index 0000000..a19a947 --- /dev/null +++ b/msg_template_builder.go @@ -0,0 +1,34 @@ +package lark + +// MsgTemplateBuilder for build template +type MsgTemplateBuilder struct { + id string + versionName string + data map[string]interface{} +} + +// NewTemplateBuilder creates a text builder +func NewTemplateBuilder() *MsgTemplateBuilder { + return &MsgTemplateBuilder{} +} + +// BindTemplate . +func (tb *MsgTemplateBuilder) BindTemplate(id, versionName string, data map[string]interface{}) *TemplateContent { + tb.id = id + tb.versionName = versionName + tb.data = data + + tc := &TemplateContent{ + Type: "template", + Data: templateData{ + TemplateID: tb.id, + TemplateVersionName: tb.versionName, + }, + } + + if data != nil { + tc.Data.TemplateVariable = tb.data + } + + return tc +} diff --git a/msg_template_builder_test.go b/msg_template_builder_test.go new file mode 100644 index 0000000..3646c2c --- /dev/null +++ b/msg_template_builder_test.go @@ -0,0 +1,21 @@ +package lark + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBindTemplate(t *testing.T) { + b := NewTemplateBuilder() + assert.Empty(t, b.id) + assert.Empty(t, b.versionName) + assert.Nil(t, b.data) + + _ = b.BindTemplate("AAqCYI07MQWh1", "1.0.0", map[string]interface{}{ + "name": "志田千陽", + }) + assert.Equal(t, "AAqCYI07MQWh1", b.id) + assert.Equal(t, "1.0.0", b.versionName) + assert.NotEmpty(t, b.data) +}