├── .gitignore ├── go.mod ├── notify_test.go ├── README.md └── notify.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/* 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/JetBlink/dingtalk-notify-go-sdk 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /notify_test.go: -------------------------------------------------------------------------------- 1 | package dingtalk_robot 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestRobot_SendMessage(t *testing.T) { 9 | //t.SkipNow() 10 | 11 | msg := map[string]interface{}{ 12 | "msgtype": "text", 13 | "text": map[string]string{ 14 | "content": "这是一条golang钉钉消息测试.", 15 | }, 16 | "at": map[string]interface{}{ 17 | "atMobiles": []string{}, 18 | "isAtAll": false, 19 | }, 20 | } 21 | 22 | robot := NewRobot(os.Getenv("ROBOT_TOKEN"), os.Getenv("ROBOT_SECRET")) 23 | if err := robot.SendMessage(msg); err != nil { 24 | t.Error(err) 25 | } 26 | } 27 | 28 | func TestRobot_SendTextMessage(t *testing.T) { 29 | robot := NewRobot(os.Getenv("ROBOT_TOKEN"), os.Getenv("ROBOT_SECRET")) 30 | if err := robot.SendTextMessage("普通文本消息", []string{}, false); err != nil { 31 | t.Error(err) 32 | } 33 | } 34 | 35 | func TestRobot_SendMarkdownMessage(t *testing.T) { 36 | robot := NewRobot(os.Getenv("ROBOT_TOKEN"), os.Getenv("ROBOT_SECRET")) 37 | err := robot.SendMarkdownMessage( 38 | "Markdown Test Title", 39 | "### Markdown 测试消息\n* 谷歌: [Google](https://www.google.com/)\n* 一张图片\n ![](https://avatars0.githubusercontent.com/u/40748346)", 40 | []string{}, 41 | false, 42 | ) 43 | if err != nil { 44 | t.Error(err) 45 | } 46 | } 47 | 48 | func TestRobot_SendLinkMessage(t *testing.T) { 49 | robot := NewRobot(os.Getenv("ROBOT_TOKEN"), os.Getenv("ROBOT_SECRET")) 50 | err := robot.SendLinkMessage( 51 | "Link Test Title", 52 | "这是一条链接测试消息", 53 | "https://github.com/JetBlink", 54 | "https://avatars0.githubusercontent.com/u/40748346", 55 | ) 56 | 57 | if err != nil { 58 | t.Error(err) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DingTalk Notify 2 | 钉钉机器人通知(支持加签)。dingtalk robot notification sdk. 3 | 4 | ## Overview 5 | 6 | * [Installation](#Installation) 7 | * [Usage](#Usage) 8 | * [获取实例](#获取实例) 9 | * [发送消息](#发送消息) 10 | * [发送原始消息](#发送原始消息) 11 | * [发送文本消息](#发送文本消息) 12 | * [发送Markdown消息](#发送markdown消息) 13 | * [发送链接消息](#发送链接消息) 14 | * [Tips](#Tips) 15 | * [官方文档](#官方文档) 16 | * [License](#license) 17 | 18 | ## Installation 19 | 20 | ``` 21 | go get github.com/JetBlink/dingtalk-notify-go-sdk 22 | ``` 23 | 24 | ## Run Test 25 | 26 | * 不带签名机器人 27 | ``` 28 | ROBOT_TOKEN=your_robot_token go test 29 | ``` 30 | 31 | * 带签名的机器人 32 | ``` 33 | ROBOT_TOKEN=your_robot_token ROBOT_SECRET=your_robot_secret go test 34 | ``` 35 | 36 | ## Usage 37 | 38 | ### 获取实例 39 | 40 | ``` 41 | import ( 42 | dingtalk_robot "github.com/JetBlink/dingtalk-notify-go-sdk" 43 | ) 44 | 45 | func main() { 46 | robot := dingtalk_robot.NewRobot("your robot token", "your robot secret") 47 | } 48 | ``` 49 | 50 | ### 发送消息 51 | 52 | #### 发送原始消息 53 | 54 | ``` 55 | msg := map[string]interface{}{ 56 | "msgtype": "text", 57 | "text": map[string]string{ 58 | "content": "这是一条golang钉钉消息测试.", 59 | }, 60 | "at": map[string]interface{}{ 61 | "atMobiles": []string{}, 62 | "isAtAll": false, 63 | }, 64 | } 65 | 66 | robot := dingtalk_robot.NewRobot(os.Getenv("ROBOT_TOKEN"), os.Getenv("ROBOT_SECRET")) 67 | if err := robot.SendMessage(msg); err != nil { 68 | t.Error(err) 69 | } 70 | ``` 71 | 72 | #### 发送文本消息 73 | 74 | ``` 75 | robot.SendTextMessage("普通文本消息", []string{}, false) 76 | ``` 77 | 78 | #### 发送Markdown消息 79 | 80 | ``` 81 | robot.SendMarkdownMessage( 82 | "Markdown Test Title", 83 | "### Markdown 测试消息\n* 谷歌: [Google](https://www.google.com/)\n* 一张图片\n ![](https://avatars0.githubusercontent.com/u/40748346)", 84 | []string{}, 85 | false, 86 | ) 87 | ``` 88 | 89 | #### 发送链接消息 90 | 91 | ``` 92 | robot.SendLinkMessage( 93 | "Link Test Title", 94 | "这是一条链接测试消息", 95 | "https://github.com/JetBlink", 96 | "https://avatars0.githubusercontent.com/u/40748346", 97 | ) 98 | ``` 99 | 100 | ### Tips 101 | 102 | 文本消息和Markdown消息都支持**@指定手机号**和**@所有人**,参数位置见具体方法 103 | 104 | ## 官方文档 105 | 106 | * [自定义机器人](https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq) 107 | > 每个机器人每分钟最多发送**20条**。 108 | 109 | ## License 110 | 111 | [MIT](https://opensource.org/licenses/MIT) 112 | -------------------------------------------------------------------------------- /notify.go: -------------------------------------------------------------------------------- 1 | package dingtalk_robot 2 | 3 | import ( 4 | "bytes" 5 | "crypto/hmac" 6 | "crypto/sha256" 7 | "encoding/base64" 8 | "encoding/json" 9 | "fmt" 10 | "io/ioutil" 11 | "net/http" 12 | "net/url" 13 | "time" 14 | ) 15 | 16 | func NewRobot(token, secret string) *Robot { 17 | return &Robot{ 18 | token: token, 19 | secret: secret, 20 | } 21 | } 22 | 23 | func sign(t int64, secret string) string { 24 | strToHash := fmt.Sprintf("%d\n%s", t, secret) 25 | hmac256 := hmac.New(sha256.New, []byte(secret)) 26 | hmac256.Write([]byte(strToHash)) 27 | data := hmac256.Sum(nil) 28 | return base64.StdEncoding.EncodeToString(data) 29 | } 30 | 31 | type Robot struct { 32 | token, secret string 33 | } 34 | 35 | func (robot *Robot) SendMessage(msg interface{}) error { 36 | body := bytes.NewBuffer(nil) 37 | err := json.NewEncoder(body).Encode(msg) 38 | if err != nil { 39 | return fmt.Errorf("msg json failed, msg: %v, err: %v", msg, err.Error()) 40 | } 41 | 42 | value := url.Values{} 43 | value.Set("access_token", robot.token) 44 | if robot.secret != "" { 45 | t := time.Now().UnixNano() / 1e6 46 | value.Set("timestamp", fmt.Sprintf("%d", t)) 47 | value.Set("sign", sign(t, robot.secret)) 48 | } 49 | 50 | request, err := http.NewRequest(http.MethodPost, "https://oapi.dingtalk.com/robot/send", body) 51 | if err != nil { 52 | return fmt.Errorf("error request: %v", err.Error()) 53 | } 54 | request.URL.RawQuery = value.Encode() 55 | request.Header.Add("Content-Type", "application/json;charset=utf-8") 56 | res, err := (&http.Client{}).Do(request) 57 | if err != nil { 58 | return fmt.Errorf("send dingTalk message failed, error: %v", err.Error()) 59 | } 60 | defer func() { _ = res.Body.Close() }() 61 | result, err := ioutil.ReadAll(res.Body) 62 | 63 | if res.StatusCode != 200 { 64 | return fmt.Errorf("send dingTalk message failed, %s", httpError(request, res, result, "http code is not 200")) 65 | } 66 | if err != nil { 67 | return fmt.Errorf("send dingTalk message failed, %s", httpError(request, res, result, err.Error())) 68 | } 69 | 70 | type response struct { 71 | ErrCode int `json:"errcode"` 72 | } 73 | var ret response 74 | 75 | if err := json.Unmarshal(result, &ret); err != nil { 76 | return fmt.Errorf("send dingTalk message failed, %s", httpError(request, res, result, err.Error())) 77 | } 78 | 79 | if ret.ErrCode != 0 { 80 | return fmt.Errorf("send dingTalk message failed, %s", httpError(request, res, result, "errcode is not 0")) 81 | } 82 | 83 | return nil 84 | } 85 | 86 | func httpError(request *http.Request, response *http.Response, body []byte, error string) string { 87 | return fmt.Sprintf( 88 | "http request failure, error: %s, status code: %d, %s %s, body:\n%s", 89 | error, 90 | response.StatusCode, 91 | request.Method, 92 | request.URL.String(), 93 | string(body), 94 | ) 95 | } 96 | 97 | func (robot *Robot) SendTextMessage(content string, atMobiles []string, isAtAll bool) error { 98 | msg := map[string]interface{}{ 99 | "msgtype": "text", 100 | "text": map[string]string{ 101 | "content": content, 102 | }, 103 | "at": map[string]interface{}{ 104 | "atMobiles": atMobiles, 105 | "isAtAll": isAtAll, 106 | }, 107 | } 108 | 109 | return robot.SendMessage(msg) 110 | } 111 | 112 | func (robot *Robot) SendMarkdownMessage(title string, text string, atMobiles []string, isAtAll bool) error { 113 | msg := map[string]interface{}{ 114 | "msgtype": "markdown", 115 | "markdown": map[string]string{ 116 | "title": title, 117 | "text": text, 118 | }, 119 | "at": map[string]interface{}{ 120 | "atMobiles": atMobiles, 121 | "isAtAll": isAtAll, 122 | }, 123 | } 124 | 125 | return robot.SendMessage(msg) 126 | } 127 | 128 | func (robot *Robot) SendLinkMessage(title string, text string, messageUrl string, picUrl string) error { 129 | msg := map[string]interface{}{ 130 | "msgtype": "link", 131 | "link": map[string]string{ 132 | "title": title, 133 | "text": text, 134 | "messageUrl": messageUrl, 135 | "picUrl": picUrl, 136 | }, 137 | } 138 | 139 | return robot.SendMessage(msg) 140 | } 141 | --------------------------------------------------------------------------------