-
Notifications
You must be signed in to change notification settings - Fork 1
/
bot.go
174 lines (146 loc) · 3.55 KB
/
bot.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package wecombot
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)
// Bot 企业微信群机器人
type Bot struct {
webhookURL string
key string
threadSafe bool
reqbuf *bytes.Buffer
client *http.Client
}
// NewBot 返回企业微信群机器人实例
func NewBot(key string, opts ...func(*Bot)) *Bot {
bot := Bot{
webhookURL: fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=%s", key),
key: key,
client: http.DefaultClient,
}
for _, setter := range opts {
setter(&bot)
}
if !bot.threadSafe {
bot.reqbuf = bytes.NewBuffer(nil)
}
return &bot
}
// WithThreadSafe 设置线程安全模式
func WithThreadSafe() func(*Bot) {
return func(bot *Bot) {
bot.threadSafe = true
}
}
// WithHttpClient 设置 HTTP 客户端
func WithHttpClient(c *http.Client) func(*Bot) {
return func(bot *Bot) {
bot.client = c
}
}
var jsonReqHeader = map[string]string{
"Content-Type": "application/json",
}
func (bot *Bot) send(msg interface{}) (err error) {
var reqBody *bytes.Buffer
if bot.threadSafe {
reqBody = bytes.NewBuffer(nil)
} else {
reqBody = bot.reqbuf
}
defer reqBody.Reset()
if err = json.NewEncoder(reqBody).Encode(msg); err != nil {
return err
}
var resData resData
if err = bot.doPost(bot.webhookURL, jsonReqHeader, reqBody, &resData); err != nil {
return err
}
return resData.ToError()
}
// isSuccess 返回 http 请求是否成功
func isSuccess(statusCode int) bool {
return statusCode >= http.StatusOK && statusCode < http.StatusMultipleChoices
}
func (bot *Bot) doPost(url string, reqHeader map[string]string, reqBody io.Reader, resData interface{}) error {
req, err := http.NewRequest(http.MethodPost, url, reqBody)
if err != nil {
return err
}
for k, v := range reqHeader {
req.Header.Set(k, v)
}
res, err := bot.client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if !isSuccess(res.StatusCode) {
return fmt.Errorf("server response abnormality: %d", res.StatusCode)
}
return json.NewDecoder(res.Body).Decode(resData)
}
type resData struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
func (rd *resData) ToError() error {
if rd.ErrCode == 0 {
return nil
}
return NewResError(rd.ErrCode, rd.ErrMsg)
}
// ResError 响应错误
type ResError struct {
errCode int
errMsg string
}
// NewResError 返回响应错误实例
func NewResError(code int, msg string) error {
return &ResError{
errCode: code,
errMsg: msg,
}
}
// ErrCode 返回错误码
func (e *ResError) ErrCode() int {
return e.errCode
}
// ErrMsg 返回错误消息内容
func (e *ResError) ErrMsg() string {
return e.errMsg
}
// Error 返回文本形式的错误描述
func (e *ResError) Error() string {
return fmt.Sprintf("[%d]%s", e.errCode, e.errMsg)
}
// MsgType 消息类型
type MsgType string
const (
// TextMsgType 文本类型
TextMsgType MsgType = "text"
// MarkdownMsgType Markdown 类型
MarkdownMsgType MsgType = "markdown"
// ImageMsgType 图片类型
ImageMsgType MsgType = "image"
// NewsMsgType 图文类型
NewsMsgType MsgType = "news"
// FileMsgType 文件类型
FileMsgType MsgType = "file"
// VoiceMsgType 语音类型
VoiceMsgType MsgType = "voice"
// TemplateCardMsgType 模板卡片类型
TemplateCardMsgType MsgType = "template_card"
)
// ExtractKey 返回 webhook 中包含的 key 信息。若 URL 地址无效,或 URL 中不包含 key 参数,则返回空字符串。
func ExtractKey(webhookURL string) (key string) {
one, err := url.Parse(webhookURL)
if err != nil {
return ""
}
return one.Query().Get("key")
}