From 1c35b0f68a693f97572cbf4b62144d8edb2e61d9 Mon Sep 17 00:00:00 2001 From: David Zhang Date: Tue, 2 Jan 2024 21:16:32 +0800 Subject: [PATCH] feat: support card callback --- README.md | 60 ++++++++++++++++++++++++++++++++++------------ card.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++ card_test.go | 43 +++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 ++-- 5 files changed, 158 insertions(+), 18 deletions(-) create mode 100644 card.go create mode 100644 card_test.go diff --git a/README.md b/README.md index 67fc761..7cfe9d7 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,14 @@ [![build](https://github.com/go-lark/lark-gin/actions/workflows/ci.yml/badge.svg)](https://github.com/go-lark/lark-gin/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/go-lark/lark-gin/branch/main/graph/badge.svg?token=MQL8MFPF2Q)](https://codecov.io/gh/go-lark/lark-gin) -Gin Middleware for go-lark. +Gin Middlewares for go-lark. -NOTICE: Only URL challenge and incoming message event (schema 1.0) are supported. -Other events will be supported with future v2 version with event schema 2.0. +## Supported events + +- URL challenge for general events and card callback +- Event v2 (Schema 2.0) +- Card Callback +- (Legacy) Incoming message event (Schema 1.0) ## Installation @@ -29,19 +33,30 @@ import ( func main() { r := gin.Default() - middleware := larkgin.NewLarkMiddleware() + + // lark server challenge r.Use(middleware.LarkChallengeHandler()) - // Event Schema 1.0, for older bots - r.Use(middleware.LarkMessageHandler()) - // Event Scheme 2.0, for newer bots - r.Use(middleware.LarkEventHandler()) - - r.POST("/", func(c *gin.Context) { - if msg, ok := middleware.GetMessage(c); ok { // => returns `*lark.EventMessage` - fmt.Println(msg.Event.Text) - } - }) + + // all supported events + eventGroup := r.Group("/event") + { + eventGroup.Use(middleware.LarkEventHandler()) + eventGroup.POST("/", func(c *gin.Context) { + if event, ok := middleware.GetEvent(e); ok { // => returns `*lark.EventV2` + } + }) + } + + // card callback only + cardGroup := r.Group("/card") + { + cardGroup.Use(middleware.LarkCardHandler()) + cardGroup.POST("/callback", func(c *gin.Context) { + if card, ok := middleware.GetCardCallback(c); ok { // => returns `*lark.EventCardCallback` + } + }) + } } ``` @@ -70,6 +85,19 @@ r.POST("/", func(c *gin.Context) { }) ``` +### Card Callback + +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) { + if card, ok := middleware.GetCardCallback(c); ok { + } +}) +``` + ### URL Binding Only bind specific URL for events: @@ -85,10 +113,12 @@ middleware.WithTokenVerfication("asodjiaoijoi121iuhiaud") ### Encryption +> Notice: encryption is not available for card callback, due to restriction from Lark Open Platform. + ```go middleware.WithEncryption("1231asda") ``` ## About -Copyright (c) go-lark Developers, 2018-2022. +Copyright (c) go-lark Developers, 2018-2024. diff --git a/card.go b/card.go new file mode 100644 index 0000000..89122f7 --- /dev/null +++ b/card.go @@ -0,0 +1,67 @@ +package larkgin + +import ( + "crypto/sha1" + "encoding/json" + "fmt" + "log" + "strings" + + "github.com/gin-gonic/gin" + "github.com/go-lark/lark" +) + +// GetCardCallback from gin context +func (opt LarkMiddleware) GetCardCallback(c *gin.Context) (*lark.EventCardCallback, bool) { + if card, ok := c.Get(opt.cardKey); ok { + msg, ok := card.(lark.EventCardCallback) + return &msg, ok + } + + return nil, false +} + +// LarkCardHandler card callback handler +// Encryption is automatically ignored, because it's not supported officially +func (opt LarkMiddleware) LarkCardHandler() gin.HandlerFunc { + return func(c *gin.Context) { + defer c.Next() + body, err := fetchBody(c) + if err != nil { + return + } + var inputBody = body + + var event lark.EventCardCallback + err = json.Unmarshal(inputBody, &event) + if err != nil { + log.Println(err) + return + } + if opt.enableTokenVerification { + nonce := c.Request.Header.Get("X-Lark-Request-Nonce") + timestamp := c.Request.Header.Get("X-Lark-Request-Timestamp") + signature := c.Request.Header.Get("X-Lark-Signature") + token := opt.cardSignature(nonce, timestamp, string(body), opt.verificationToken) + log.Println(token, signature) + if signature != token { + log.Println("Token verification failed") + return + } + } + c.Set(opt.cardKey, event) + } +} + +func (opt LarkMiddleware) cardSignature(nonce string, timestamp string, body string, token string) string { + var b strings.Builder + b.WriteString(timestamp) + b.WriteString(nonce) + b.WriteString(token) + b.WriteString(body) + bs := []byte(b.String()) + h := sha1.New() + h.Write(bs) + bs = h.Sum(nil) + return fmt.Sprintf("%x", bs) +} diff --git a/card_test.go b/card_test.go new file mode 100644 index 0000000..ee62d23 --- /dev/null +++ b/card_test.go @@ -0,0 +1,43 @@ +package larkgin + +import ( + "testing" + + "github.com/gin-gonic/gin" + "github.com/go-lark/lark" + "github.com/stretchr/testify/assert" +) + +func TestCardCallback(t *testing.T) { + var ( + r = gin.Default() + middleware = NewLarkMiddleware() + + ok bool + event *lark.EventCardCallback + ) + r.Use(middleware.LarkCardHandler()) + + card := map[string]interface{}{ + "app_id": "fake_app_id", + "open_id": "fake_open_id", + "user_id": "f123f456", + "open_message_id": "om_8169c75fbae56c6bebb7e914b92253b4", + "open_chat_id": "fake_oc_id", + "tenant_key": "1068767a888dd740", + "token": "c-2fa8cd831bc83e6350b5be32eb24d2863be4bc5b", + "action": map[string]interface{}{ + "tag": "button", + "value": map[string]interface{}{"action": "1"}, + }, + } + r.POST("/", func(c *gin.Context) { + event, ok = middleware.GetCardCallback(c) + t.Log(event, ok) + }) + performRequest(r, "POST", "/", card) + assert.True(t, ok) + if assert.NotNil(t, event) { + assert.Equal(t, "button", event.Action.Tag) + } +} diff --git a/go.mod b/go.mod index 3a2ba47..bf032a7 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,6 @@ go 1.13 require ( github.com/gin-gonic/gin v1.7.1 - github.com/go-lark/lark v1.7.0-beta.4 + github.com/go-lark/lark v1.13.1 github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index ba34090..9374d1e 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.7.1 h1:qC89GU3p8TvKWMAVhEpmpB2CIb1hnqt2UdKZaP93mS8= github.com/gin-gonic/gin v1.7.1/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= -github.com/go-lark/lark v1.7.0-beta.4 h1:3Sx5swdA69TAH6uoT0yuED9cCaBLeJ7TjMhWyVVIWXk= -github.com/go-lark/lark v1.7.0-beta.4/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM= +github.com/go-lark/lark v1.13.1 h1:Vm2SEZdkYnyeuxHopzkTbNsr+nnjs9mCTWenML6JP8Q= +github.com/go-lark/lark v1.13.1/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=