Skip to content

Commit 2886ad5

Browse files
sy-recordsLinkinStars
authored andcommitted
feat: Support Ding talk Notification
1 parent f10caf7 commit 2886ad5

13 files changed

+929
-1
lines changed

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ The Connector plugin helps us to implement third-party login functionality. For
2525

2626
The Storage plugin helps us to upload files to third-party storage. For example: Aliyun OSS or AWS S3.
2727

28-
- [x] [Aliyun](https://github.com/apache/incubator-answer-plugins/tree/main/storage-aliyunoss)
28+
- [x] [Aliyun OSS](https://github.com/apache/incubator-answer-plugins/tree/main/storage-aliyunoss)
29+
- [x] [Tencentyun COS](https://github.com/apache/incubator-answer-plugins/tree/main/storage-tencentyuncos)
2930
- [x] [S3](https://github.com/apache/incubator-answer-plugins/tree/main/storage-s3)
3031

3132
### Cache
@@ -53,6 +54,8 @@ Using the third-party user system to manage users. For example: WeCom
5354
The Notification plugin helps us to send messages to third-party notification systems. For example: Slack.
5455

5556
- [x] [Slack](https://github.com/apache/incubator-answer-plugins/tree/main/notification-slack)
57+
- [x] [Lark](https://github.com/apache/incubator-answer-plugins/tree/main/notification-lark)
58+
- [x] [Ding talk](https://github.com/apache/incubator-answer-plugins/tree/main/notification-dingtalk)
5659

5760
### Route
5861

notification-dingtalk/README.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Ding talk Notification
2+
3+
## Feature
4+
5+
- Send message to Ding talk
6+
7+
## Config
8+
9+
> Config Webhook URL and open the notification
10+
11+
- Webhook URL: such as `https://oapi.dingtalk.com/robot/send?access_token=xxxxxx`
12+
13+
## Preview
14+
15+
![Ding talk Config](./docs/dingtalk-config.png)
16+
17+
## Document
18+
19+
- https://open.dingtalk.com/document/robots/custom-robot-access
20+
- https://open.dingtalk.com/document/orgapp/custom-bot-send-message-type

notification-dingtalk/config.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package dingtalk
21+
22+
import (
23+
"encoding/json"
24+
25+
"github.com/apache/incubator-answer-plugins/notification-dingtalk/i18n"
26+
"github.com/apache/incubator-answer/plugin"
27+
)
28+
29+
type NotificationConfig struct {
30+
Notification bool `json:"notification"`
31+
}
32+
33+
func (n *Notification) ConfigFields() []plugin.ConfigField {
34+
return []plugin.ConfigField{
35+
{
36+
Name: "notification",
37+
Type: plugin.ConfigTypeSwitch,
38+
Title: plugin.MakeTranslator(i18n.ConfigNotificationTitle),
39+
Description: plugin.MakeTranslator(i18n.ConfigNotificationDescription),
40+
UIOptions: plugin.ConfigFieldUIOptions{
41+
Label: plugin.MakeTranslator(i18n.ConfigNotificationLabel),
42+
},
43+
Value: n.Config.Notification,
44+
},
45+
}
46+
}
47+
48+
func (n *Notification) ConfigReceiver(config []byte) error {
49+
c := &NotificationConfig{}
50+
_ = json.Unmarshal(config, c)
51+
n.Config = c
52+
return nil
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package dingtalk
21+
22+
import (
23+
"embed"
24+
"github.com/apache/incubator-answer-plugins/util"
25+
"github.com/go-resty/resty/v2"
26+
"strings"
27+
28+
dingtalkI18n "github.com/apache/incubator-answer-plugins/notification-dingtalk/i18n"
29+
"github.com/apache/incubator-answer/plugin"
30+
"github.com/segmentfault/pacman/i18n"
31+
"github.com/segmentfault/pacman/log"
32+
)
33+
34+
//go:embed info.yaml
35+
var Info embed.FS
36+
37+
type Notification struct {
38+
Config *NotificationConfig
39+
UserConfigCache *UserConfigCache
40+
}
41+
42+
func init() {
43+
uc := &Notification{
44+
Config: &NotificationConfig{},
45+
UserConfigCache: NewUserConfigCache(),
46+
}
47+
plugin.Register(uc)
48+
}
49+
50+
func (n *Notification) Info() plugin.Info {
51+
info := &util.Info{}
52+
info.GetInfo(Info)
53+
54+
return plugin.Info{
55+
Name: plugin.MakeTranslator(dingtalkI18n.InfoName),
56+
SlugName: info.SlugName,
57+
Description: plugin.MakeTranslator(dingtalkI18n.InfoDescription),
58+
Author: info.Author,
59+
Version: info.Version,
60+
Link: info.Link,
61+
}
62+
}
63+
64+
// GetNewQuestionSubscribers returns the subscribers of the new question notification
65+
func (n *Notification) GetNewQuestionSubscribers() (userIDs []string) {
66+
for userID, conf := range n.UserConfigCache.userConfigMapping {
67+
if conf.AllNewQuestions {
68+
userIDs = append(userIDs, userID)
69+
}
70+
}
71+
return userIDs
72+
}
73+
74+
// Notify sends a notification to the user
75+
func (n *Notification) Notify(msg plugin.NotificationMessage) {
76+
log.Debugf("try to send notification %+v", msg)
77+
78+
if !n.Config.Notification {
79+
return
80+
}
81+
82+
// get user config
83+
userConfig, err := n.getUserConfig(msg.ReceiverUserID)
84+
if err != nil {
85+
log.Errorf("get user config failed: %v", err)
86+
return
87+
}
88+
if userConfig == nil {
89+
log.Debugf("user %s has no config", msg.ReceiverUserID)
90+
return
91+
}
92+
93+
// check if the notification is enabled
94+
switch msg.Type {
95+
case plugin.NotificationNewQuestion:
96+
if !userConfig.AllNewQuestions {
97+
log.Debugf("user %s not config the new question", msg.ReceiverUserID)
98+
return
99+
}
100+
case plugin.NotificationNewQuestionFollowedTag:
101+
if !userConfig.NewQuestionsForFollowingTags {
102+
log.Debugf("user %s not config the new question followed tag", msg.ReceiverUserID)
103+
return
104+
}
105+
default:
106+
if !userConfig.InboxNotifications {
107+
log.Debugf("user %s not config the inbox notification", msg.ReceiverUserID)
108+
return
109+
}
110+
}
111+
112+
log.Debugf("user %s config the notification", msg.ReceiverUserID)
113+
114+
if len(userConfig.WebhookURL) == 0 {
115+
log.Errorf("user %s has no webhook url", msg.ReceiverUserID)
116+
return
117+
}
118+
119+
notificationMsg, notificationTitle := renderNotification(msg)
120+
// no need to send empty message
121+
if len(notificationMsg) == 0 {
122+
log.Debugf("this type of notification will be drop, the type is %s", msg.Type)
123+
return
124+
}
125+
126+
// Create a Resty Client
127+
client := resty.New()
128+
resp, err := client.R().
129+
SetHeader("Content-Type", "application/json").
130+
SetBody(NewWebhookReq(notificationMsg, notificationTitle)).
131+
Post(userConfig.WebhookURL)
132+
133+
if err != nil {
134+
log.Errorf("send message failed: %v %v", err, resp)
135+
} else {
136+
log.Infof("send message to %s success, resp: %s", msg.ReceiverUserID, resp.String())
137+
}
138+
}
139+
140+
func renderNotification(msg plugin.NotificationMessage) (string, string) {
141+
lang := i18n.Language(msg.ReceiverLang)
142+
switch msg.Type {
143+
case plugin.NotificationUpdateQuestion:
144+
return plugin.TranslateWithData(lang, dingtalkI18n.TplUpdateQuestion, msg), plugin.TranslateWithData(lang, dingtalkI18n.TplUpdateQuestionTitle, nil)
145+
case plugin.NotificationAnswerTheQuestion:
146+
return plugin.TranslateWithData(lang, dingtalkI18n.TplAnswerTheQuestion, msg), plugin.TranslateWithData(lang, dingtalkI18n.TplAnswerTheQuestionTitle, nil)
147+
case plugin.NotificationUpdateAnswer:
148+
return plugin.TranslateWithData(lang, dingtalkI18n.TplUpdateAnswer, msg), plugin.TranslateWithData(lang, dingtalkI18n.TplUpdateAnswerTitle, nil)
149+
case plugin.NotificationAcceptAnswer:
150+
return plugin.TranslateWithData(lang, dingtalkI18n.TplAcceptAnswer, msg), plugin.TranslateWithData(lang, dingtalkI18n.TplAcceptAnswerTitle, nil)
151+
case plugin.NotificationCommentQuestion:
152+
return plugin.TranslateWithData(lang, dingtalkI18n.TplCommentQuestion, msg), plugin.TranslateWithData(lang, dingtalkI18n.TplCommentQuestionTitle, nil)
153+
case plugin.NotificationCommentAnswer:
154+
return plugin.TranslateWithData(lang, dingtalkI18n.TplCommentAnswer, msg), plugin.TranslateWithData(lang, dingtalkI18n.TplCommentAnswerTitle, nil)
155+
case plugin.NotificationReplyToYou:
156+
return plugin.TranslateWithData(lang, dingtalkI18n.TplReplyToYou, msg), plugin.TranslateWithData(lang, dingtalkI18n.TplReplyToYouTitle, nil)
157+
case plugin.NotificationMentionYou:
158+
return plugin.TranslateWithData(lang, dingtalkI18n.TplMentionYou, msg), plugin.TranslateWithData(lang, dingtalkI18n.TplMentionYouTitle, nil)
159+
case plugin.NotificationInvitedYouToAnswer:
160+
return plugin.TranslateWithData(lang, dingtalkI18n.TplInvitedYouToAnswer, msg), plugin.TranslateWithData(lang, dingtalkI18n.TplInvitedYouToAnswerTitle, nil)
161+
case plugin.NotificationNewQuestion, plugin.NotificationNewQuestionFollowedTag:
162+
msg.QuestionTags = strings.Join(strings.Split(msg.QuestionTags, ","), ", ")
163+
return plugin.TranslateWithData(lang, dingtalkI18n.TplNewQuestion, msg), plugin.TranslateWithData(lang, dingtalkI18n.TplNewQuestionTitle, nil)
164+
}
165+
return "", ""
166+
}
247 KB
Loading

notification-dingtalk/go.mod

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
module dingtalk
2+
3+
go 1.21.3
4+
5+
require (
6+
github.com/apache/incubator-answer v1.4.0
7+
github.com/apache/incubator-answer-plugins/util v1.0.2
8+
github.com/go-resty/resty/v2 v2.15.3
9+
github.com/segmentfault/pacman v1.0.5-0.20230822083413-c0075a2d401f
10+
)
11+
12+
require (
13+
github.com/LinkinStars/go-i18n/v2 v2.2.2 // indirect
14+
github.com/aymerick/douceur v0.2.0 // indirect
15+
github.com/bytedance/sonic v1.9.1 // indirect
16+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
17+
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
18+
github.com/gin-contrib/sse v0.1.0 // indirect
19+
github.com/gin-gonic/gin v1.9.1 // indirect
20+
github.com/go-playground/locales v0.14.1 // indirect
21+
github.com/go-playground/universal-translator v0.18.1 // indirect
22+
github.com/go-playground/validator/v10 v10.14.0 // indirect
23+
github.com/goccy/go-json v0.10.2 // indirect
24+
github.com/google/wire v0.5.0 // indirect
25+
github.com/gorilla/css v1.0.0 // indirect
26+
github.com/json-iterator/go v1.1.12 // indirect
27+
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
28+
github.com/kr/text v0.2.0 // indirect
29+
github.com/leodido/go-urn v1.2.4 // indirect
30+
github.com/mattn/go-isatty v0.0.19 // indirect
31+
github.com/microcosm-cc/bluemonday v1.0.21 // indirect
32+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
33+
github.com/modern-go/reflect2 v1.0.2 // indirect
34+
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
35+
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20230516093754-b76aef1c1150 // indirect
36+
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
37+
github.com/ugorji/go/codec v1.2.11 // indirect
38+
golang.org/x/arch v0.3.0 // indirect
39+
golang.org/x/crypto v0.25.0 // indirect
40+
golang.org/x/net v0.27.0 // indirect
41+
golang.org/x/sys v0.22.0 // indirect
42+
golang.org/x/text v0.16.0 // indirect
43+
google.golang.org/protobuf v1.30.0 // indirect
44+
gopkg.in/yaml.v2 v2.4.0 // indirect
45+
gopkg.in/yaml.v3 v3.0.1 // indirect
46+
sigs.k8s.io/yaml v1.3.0 // indirect
47+
)

0 commit comments

Comments
 (0)