Skip to content

Commit 3d53182

Browse files
authored
Merge pull request #7 from ipfs-force-community/feat/lark
feat: support lark
2 parents 87ba08a + b2a7c54 commit 3d53182

File tree

7 files changed

+188
-2
lines changed

7 files changed

+188
-2
lines changed

cmd/main.go

+4
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ func subscribe(webHandler *web.Handler,
181181
paths = append(paths, fmt.Sprintf("http://%s:%s/wechat/%s/send", host, port, name))
182182
continue
183183
}
184+
if strings.Contains(target.URL.String(), "open.larksuite.com") {
185+
paths = append(paths, fmt.Sprintf("http://%s:%s/lark/%s/send", host, port, name))
186+
continue
187+
}
184188
paths = append(paths, fmt.Sprintf("http://%s:%s/dingtalk/%s/send", host, port, name))
185189
}
186190
configLogger.Log("msg", "Webhook urls for prometheus alertmanager", "urls", strings.Join(paths, " "))

cmd/main_test.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ import (
1313

1414
func TestSend(t *testing.T) {
1515
t.SkipNow()
16-
// urls := []string{"http://localhost:8060/dingtalk/webhook1/send", "http://localhost:8060/wechat/webhook2/send"}
17-
urls := []string{"http://localhost:8060/wechat/webhook2/send"}
16+
urls := []string{
17+
"http://localhost:8060/dingtalk/webhook1/send",
18+
"http://localhost:8060/wechat/webhook2/send",
19+
"http://localhost:8060/lark/webhook3/send",
20+
}
1821
cli := http.DefaultClient
1922
reqData, err := os.ReadFile("./test_data.json")
2023
assert.NoError(t, err)

config.example.yml

+2
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ targets:
2020
url: https://oapi.dingtalk.com/robot/send?access_token=86940410bc5c5a43e7c57f2cea6f1c186bcbc16db87f2a006a7b969aae2bb2b6
2121
webhook2:
2222
url: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=d6493921-e728-432b-bf65-560b98a2f275
23+
webhook3:
24+
url: https://open.larksuite.com/open-apis/bot/v2/hook/a44c7c63-4fd2-4625-87ce-ac4f7c639688

notifier/notification.go

+33
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,39 @@ func (r *DingNotificationBuilder) BuildWeChat(m *models.WebhookMessage) (*models
116116
return notification, nil
117117
}
118118

119+
func (r *DingNotificationBuilder) BuildLark(m *models.WebhookMessage) (*models.LarkNotification, error) {
120+
if r.target.Mention != nil {
121+
m.AtMobiles = append(m.AtMobiles, r.target.Mention.Mobiles...)
122+
}
123+
124+
content, err := r.renderText(m)
125+
if err != nil {
126+
return nil, err
127+
}
128+
129+
notification := &models.LarkNotification{
130+
MessageType: "interactive",
131+
Card: models.LarkNotificationCard{
132+
Elements: []models.LarkNotificationElement{
133+
{
134+
Tag: "markdown",
135+
Content: content,
136+
},
137+
},
138+
},
139+
}
140+
141+
// Build mention
142+
if r.target.Mention != nil {
143+
notification.At = &models.DingTalkNotificationAt{
144+
IsAtAll: r.target.Mention.All,
145+
AtMobiles: r.target.Mention.Mobiles,
146+
}
147+
}
148+
149+
return notification, nil
150+
}
151+
119152
func SendNotification(notification any, httpClient *http.Client, target *config.Target) (*models.DingTalkNotificationResponse, error) {
120153
targetURL := *target.URL
121154
// Calculate signature when secret is provided

pkg/models/dingtalk.go

+21
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ package models
33
type DingTalkNotificationResponse struct {
44
ErrorMessage string `json:"errmsg"`
55
ErrorCode int `json:"errcode"`
6+
7+
// lark response fields
8+
Code int `json:"code"`
9+
Msg string `json:"msg"`
610
}
711

812
type DingTalkNotification struct {
@@ -65,3 +69,20 @@ type WeChatNotification struct {
6569
type WeChatNotificationMarkdown struct {
6670
Content string `json:"content"`
6771
}
72+
73+
// //////// lark ////////////
74+
// https://open.larksuite.com/document/client-docs/bot-v3/add-custom-bot
75+
type LarkNotification struct {
76+
MessageType string `json:"msg_type"`
77+
Card LarkNotificationCard `json:"card"`
78+
At *DingTalkNotificationAt `json:"at,omitempty"`
79+
}
80+
81+
type LarkNotificationCard struct {
82+
Elements []LarkNotificationElement `json:"elements"`
83+
}
84+
85+
type LarkNotificationElement struct {
86+
Tag string `json:"tag"`
87+
Content string `json:"content"`
88+
}

web/lark/lark.go

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package lark
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
"net/http"
7+
"strings"
8+
"sync"
9+
10+
"github.com/go-chi/chi/v5"
11+
"github.com/go-chi/chi/v5/middleware"
12+
"github.com/go-kit/log"
13+
"github.com/go-kit/log/level"
14+
15+
"github.com/timonwong/prometheus-webhook-dingtalk/config"
16+
"github.com/timonwong/prometheus-webhook-dingtalk/notifier"
17+
"github.com/timonwong/prometheus-webhook-dingtalk/pkg/chilog"
18+
"github.com/timonwong/prometheus-webhook-dingtalk/pkg/models"
19+
"github.com/timonwong/prometheus-webhook-dingtalk/template"
20+
)
21+
22+
type API struct {
23+
// Protect against config, template and http client
24+
mtx sync.RWMutex
25+
26+
conf *config.Config
27+
tmpl *template.Template
28+
targets map[string]config.Target
29+
httpClient *http.Client
30+
logger log.Logger
31+
}
32+
33+
func NewAPI(logger log.Logger) *API {
34+
return &API{
35+
logger: logger,
36+
}
37+
}
38+
39+
func (api *API) Update(conf *config.Config, tmpl *template.Template) {
40+
api.mtx.Lock()
41+
defer api.mtx.Unlock()
42+
43+
api.conf = conf
44+
api.tmpl = tmpl
45+
46+
tmp := make(map[string]config.Target)
47+
for k, v := range conf.Targets {
48+
if strings.Contains(v.URL.Host, "open.larksuite.com") {
49+
tmp[k] = v
50+
}
51+
}
52+
api.targets = tmp
53+
54+
api.httpClient = &http.Client{
55+
Transport: &http.Transport{
56+
Proxy: http.ProxyFromEnvironment,
57+
DisableKeepAlives: true,
58+
},
59+
}
60+
}
61+
62+
func (api *API) Routes() chi.Router {
63+
router := chi.NewRouter()
64+
router.Use(middleware.RealIP)
65+
router.Use(middleware.RequestLogger(&chilog.KitLogger{Logger: api.logger}))
66+
router.Use(middleware.Recoverer)
67+
router.Post("/{name}/send", api.serveSend)
68+
return router
69+
}
70+
71+
func (api *API) serveSend(w http.ResponseWriter, r *http.Request) {
72+
api.mtx.RLock()
73+
targets := api.targets
74+
conf := api.conf
75+
tmpl := api.tmpl
76+
httpClient := api.httpClient
77+
api.mtx.RUnlock()
78+
79+
targetName := chi.URLParam(r, "name")
80+
logger := log.With(api.logger, "target", targetName)
81+
82+
target, ok := targets[targetName]
83+
if !ok {
84+
level.Warn(logger).Log("msg", "target not found")
85+
http.NotFound(w, r)
86+
return
87+
}
88+
89+
var promMessage models.WebhookMessage
90+
if err := json.NewDecoder(r.Body).Decode(&promMessage); err != nil {
91+
level.Error(logger).Log("msg", "Cannot decode prometheus webhook JSON request", "err", err)
92+
http.Error(w, "Bad Request", http.StatusBadRequest)
93+
return
94+
}
95+
96+
builder := notifier.NewDingNotificationBuilder(tmpl, conf, &target)
97+
notification, err := builder.BuildLark(&promMessage)
98+
if err != nil {
99+
level.Error(logger).Log("msg", "Failed to build notification", "err", err)
100+
http.Error(w, "Bad Request", http.StatusBadRequest)
101+
return
102+
}
103+
104+
robotResp, err := notifier.SendNotification(notification, httpClient, &target)
105+
if err != nil {
106+
level.Error(logger).Log("msg", "Failed to send notification", "err", err)
107+
http.Error(w, "Bad Request", http.StatusBadRequest)
108+
return
109+
}
110+
111+
if robotResp.Code != 0 {
112+
level.Error(logger).Log("msg", "Failed to send notification to DingTalk", "respCode", robotResp.Code, "respMsg", robotResp.Msg)
113+
http.Error(w, "Unable to talk to DingTalk", http.StatusBadRequest)
114+
return
115+
}
116+
117+
io.WriteString(w, "OK")
118+
}

web/web.go

+5
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"github.com/timonwong/prometheus-webhook-dingtalk/template"
3939
"github.com/timonwong/prometheus-webhook-dingtalk/web/apiv1"
4040
"github.com/timonwong/prometheus-webhook-dingtalk/web/dingtalk"
41+
"github.com/timonwong/prometheus-webhook-dingtalk/web/lark"
4142
"github.com/timonwong/prometheus-webhook-dingtalk/web/ui"
4243
"github.com/timonwong/prometheus-webhook-dingtalk/web/wechat"
4344
)
@@ -69,6 +70,7 @@ type Handler struct {
6970
apiV1 *apiv1.API
7071
dingTalk *dingtalk.API
7172
weChat *wechat.API
73+
lark *lark.API
7274

7375
router chi.Router
7476
reloadCh chan chan error
@@ -123,9 +125,11 @@ func New(logger log.Logger, o *Options) *Handler {
123125
)
124126
h.dingTalk = dingtalk.NewAPI(logger)
125127
h.weChat = wechat.NewAPI(logger)
128+
h.lark = lark.NewAPI(logger)
126129

127130
router.Mount("/dingtalk", h.dingTalk.Routes())
128131
router.Mount("/wechat", h.weChat.Routes())
132+
router.Mount("/lark", h.lark.Routes())
129133

130134
if o.EnableLifecycle {
131135
router.Post("/-/reload", h.reload)
@@ -205,6 +209,7 @@ func (h *Handler) ApplyConfig(conf *config.Config, tmpl *template.Template) erro
205209
h.tmpl = tmpl
206210
h.dingTalk.Update(conf, tmpl)
207211
h.weChat.Update(conf, tmpl)
212+
h.lark.Update(conf, tmpl)
208213
return nil
209214
}
210215

0 commit comments

Comments
 (0)