Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Card template #75

Merged
merged 3 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## v1.15.0

- feat(im): support card template (#75)

## v1.14.1

- feat(api): UploadFile support uploads binary file
Expand All @@ -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

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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) {
Expand Down
25 changes: 13 additions & 12 deletions README_zhCN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` | 添加表情 | 需要先上传到飞书服务器 |

### 异常处理

Expand Down
14 changes: 14 additions & 0 deletions api_message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
13 changes: 13 additions & 0 deletions message.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 .
Expand Down Expand Up @@ -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{}
}
6 changes: 5 additions & 1 deletion message_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 10 additions & 0 deletions msg_buf.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions msg_template_builder.go
Original file line number Diff line number Diff line change
@@ -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
}
21 changes: 21 additions & 0 deletions msg_template_builder_test.go
Original file line number Diff line number Diff line change
@@ -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)
}