初始化项目

master
loser 2 years ago
parent 858ee66482
commit 05b77d99c7

77
.gitignore vendored

@ -1,7 +1,5 @@
# ---> Go
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Created by .ignore support plugin (hsz.mobi)
### Go template
# Binaries for programs and plugins
*.exe
*.exe~
@ -18,6 +16,73 @@
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
.idea/

File diff suppressed because one or more lines are too long

134
bot.go

@ -0,0 +1,134 @@
package wxworkbot
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"time"
)
func init() {
}
const (
defaultWebHookUrlTemplate = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=%s"
)
var (
ErrUnsupportedMessage = errors.New("尚不支持的消息类型")
)
type WxWorkBot struct {
Key string
WebHookUrl string
Client *http.Client
}
// 创建一个新的机器人实例
func New(botKey string) *WxWorkBot {
bot := WxWorkBot{
Key: botKey,
// 直接拼接出接口 URL
WebHookUrl: fmt.Sprintf(defaultWebHookUrlTemplate, botKey),
// 默认 5 秒超时
Client: &http.Client{Timeout: 5 * time.Second},
}
return &bot
}
// 发送消息允许的参数类型Text、Markdown、Image、News
func (bot *WxWorkBot) Send(msg interface{}) error {
msgBytes, err := marshalMessage(msg)
if err != nil {
return err
}
webHookUrl := bot.WebHookUrl
if len(webHookUrl) == 0 {
webHookUrl = fmt.Sprintf(defaultWebHookUrlTemplate, bot.Key)
}
req, err := http.NewRequest(http.MethodPost, webHookUrl, bytes.NewBuffer(msgBytes))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
resp, err := bot.Client.Do(req)
if err != nil {
return err
}
body, _ := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
var wxWorkResp wxWorkResponse
err = json.Unmarshal(body, &wxWorkResp)
if err != nil {
return err
}
if wxWorkResp.ErrorCode != 0 && wxWorkResp.ErrorMessage != "" {
return errors.New(string(body))
}
return nil
}
// 防止 < > 等 HTML 字符在 json.marshal 时被 escape
func marshal(msg interface{}) ([]byte, error) {
buf := bytes.NewBuffer([]byte{})
jsonEncoder := json.NewEncoder(buf)
jsonEncoder.SetEscapeHTML(false)
jsonEncoder.SetIndent("", "")
err := jsonEncoder.Encode(msg)
if err != nil {
return nil, nil
}
return buf.Bytes(), nil
}
// 将消息包装成企信接口要求的格式,返回 json bytes
func marshalMessage(msg interface{}) ([]byte, error) {
if text, ok := msg.(Text); ok {
textMsg := textMessage{
message: message{MsgType: "text"},
Text: text,
}
return marshal(textMsg)
}
if textMsg, ok := msg.(textMessage); ok {
textMsg.MsgType = "text"
return marshal(textMsg)
}
if markdown, ok := msg.(Markdown); ok {
markdownMsg := markdownMessage{
message: message{MsgType: "markdown"},
Markdown: markdown,
}
return marshal(markdownMsg)
}
if markdownMsg, ok := msg.(markdownMessage); ok {
markdownMsg.MsgType = "markdown"
return marshal(markdownMsg)
}
if image, ok := msg.(Image); ok {
imageMsg := imageMessage{
message: message{MsgType: "image"},
Image: image,
}
return marshal(imageMsg)
}
if imageMsg, ok := msg.(imageMessage); ok {
imageMsg.MsgType = "image"
return marshal(imageMsg)
}
if news, ok := msg.(News); ok {
newsMsg := newsMessage{
message: message{MsgType: "news"},
News: news,
}
return marshal(newsMsg)
}
if newsMsg, ok := msg.(newsMessage); ok {
newsMsg.MsgType = "news"
return marshal(newsMsg)
}
return nil, ErrUnsupportedMessage
}

@ -0,0 +1,177 @@
package wxworkbot
import (
"github.com/stretchr/testify/assert"
"net/http"
"os"
"strings"
"testing"
"time"
)
func getBotKey() string {
return os.Getenv("WXWORK_BOT_KEY")
}
func TestMarshalText(t *testing.T) {
text := Text{
Content: "广州今日天气29度大部分多云降雨概率60%",
MentionedList: []string{"wangqing", "@all"},
MentionedMobileList: []string{"13800001111", "@all"},
}
msgBytes, err := marshalMessage(text)
assert.Nil(t, err)
expected := `{"msgtype":"text","text":{"content":"广州今日天气29度大部分多云降雨概率60%","mentioned_list":["wangqing","@all"],"mentioned_mobile_list":["13800001111","@all"]}}`
msg := strings.TrimSuffix(string(msgBytes), "\n")
assert.Equal(t, expected, msg)
}
func TestMarshalTextMessage(t *testing.T) {
textMsg := textMessage{
Text: Text{
Content: "广州今日天气29度大部分多云降雨概率60%",
MentionedList: []string{"wangqing", "@all"},
MentionedMobileList: []string{"13800001111", "@all"},
},
}
msgBytes, err := marshalMessage(textMsg)
assert.Nil(t, err)
expected := `{"msgtype":"text","text":{"content":"广州今日天气29度大部分多云降雨概率60%","mentioned_list":["wangqing","@all"],"mentioned_mobile_list":["13800001111","@all"]}}`
msg := strings.TrimSuffix(string(msgBytes), "\n")
assert.Equal(t, expected, msg)
}
func TestMarshalMarkdown(t *testing.T) {
markdown := Markdown{
Content: "<font color=\"warning\">233</font>",
}
msgBytes, err := marshalMessage(markdown)
assert.Nil(t, err)
expected := `{"msgtype":"markdown","markdown":{"content":"<font color=\"warning\">233</font>"}}`
msg := strings.TrimSuffix(string(msgBytes), "\n")
assert.Equal(t, expected, msg)
}
func TestMarshalMarkdownMessage(t *testing.T) {
markdownMsg := markdownMessage{
Markdown: Markdown{
Content: "<font color=\"warning\">233</font>",
},
}
msgBytes, err := marshalMessage(markdownMsg)
assert.Nil(t, err)
expected := `{"msgtype":"markdown","markdown":{"content":"<font color=\"warning\">233</font>"}}`
msg := strings.TrimSuffix(string(msgBytes), "\n")
assert.Equal(t, expected, msg)
}
func TestMarshalImage(t *testing.T) {
image := Image{
Base64: "DATA",
MD5: "MD5",
}
msgBytes, err := marshalMessage(image)
assert.Nil(t, err)
expected := `{"msgtype":"image","image":{"base64":"DATA","md5":"MD5"}}`
msg := strings.TrimSuffix(string(msgBytes), "\n")
assert.Equal(t, expected, msg)
}
func TestMarshalImageMessage(t *testing.T) {
imageMsg := imageMessage{
Image: Image{
Base64: "DATA",
MD5: "MD5",
},
}
msgBytes, err := marshalMessage(imageMsg)
assert.Nil(t, err)
expected := `{"msgtype":"image","image":{"base64":"DATA","md5":"MD5"}}`
msg := strings.TrimSuffix(string(msgBytes), "\n")
assert.Equal(t, expected, msg)
}
func TestMarshalNews(t *testing.T) {
news := News{
Articles: []NewsArticle{
{
Title: "中秋节礼品领取",
Description: "今年中秋节公司有豪礼相送",
URL: "URL",
PicURL: "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png",
},
},
}
msgBytes, err := marshalMessage(news)
assert.Nil(t, err)
expected := `{"msgtype":"news","news":{"articles":[{"title":"中秋节礼品领取","description":"今年中秋节公司有豪礼相送","url":"URL","picurl":"http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png"}]}}`
msg := strings.TrimSuffix(string(msgBytes), "\n")
assert.Equal(t, expected, msg)
}
func TestMarshalNewsMessage(t *testing.T) {
newsMsg := newsMessage{
News: News{
Articles: []NewsArticle{
{
Title: "中秋节礼品领取",
Description: "今年中秋节公司有豪礼相送",
URL: "URL",
PicURL: "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png",
},
}},
}
msgBytes, err := marshalMessage(newsMsg)
assert.Nil(t, err)
expected := `{"msgtype":"news","news":{"articles":[{"title":"中秋节礼品领取","description":"今年中秋节公司有豪礼相送","url":"URL","picurl":"http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png"}]}}`
msg := strings.TrimSuffix(string(msgBytes), "\n")
assert.Equal(t, expected, msg)
}
func TestMarshalUnsupportedMessage(t *testing.T) {
text := struct {
Foo string
}{
Foo: "bar",
}
_, err := marshalMessage(text)
assert.Equal(t, ErrUnsupportedMessage, err)
}
func TestSendText(t *testing.T) {
bot := New(getBotKey())
err := bot.Send(Text{
Content: "测试发送文本消息",
MentionedList: []string{"wangqing", "@all"},
MentionedMobileList: []string{"13800001111", "@all"},
})
assert.Nil(t, err)
}
func TestSendWithInvalidBotKey(t *testing.T) {
textMsg := textMessage{
Text: Text{
Content: "广州今日天气29度大部分多云降雨概率60%",
MentionedList: []string{"wangqing", "@all"},
MentionedMobileList: []string{"13800001111", "@all"},
},
}
bot := New("这是一个错误的 BOT KEY")
err := bot.Send(textMsg)
assert.NotNil(t, err)
}
func TestWithCustomHttpClient(t *testing.T) {
bot := WxWorkBot{
Key: getBotKey(),
Client: &http.Client{
Timeout: 1 * time.Second,
},
}
err := bot.Send(Text{
Content: "广州今日天气29度大部分多云降雨概率60%",
MentionedList: []string{"wangqing", "@all"},
MentionedMobileList: []string{"13800001111", "@all"},
})
assert.Nil(t, err)
}

@ -0,0 +1,5 @@
module github.com/vimsucks/wxwork-bot-go
go 1.12
require github.com/stretchr/testify v1.4.0

@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

@ -0,0 +1,11 @@
package wxworkbot
type imageMessage struct {
message
Image Image `json:"image"`
}
type Image struct {
Base64 string `json:"base64"`
MD5 string `json:"md5"`
}

@ -0,0 +1,25 @@
package wxworkbot
import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
)
func TestImageMessage(t *testing.T) {
jsonString := `
{
"msgtype": "image",
"image": {
"base64": "DATA",
"md5": "MD5"
}
}
`
var imageMsg imageMessage
err := json.Unmarshal([]byte(jsonString), &imageMsg)
assert.Nil(t, err)
assert.Equal(t, imageMsg.MsgType, "image")
assert.Equal(t, imageMsg.Image.Base64, "DATA")
assert.Equal(t, imageMsg.Image.MD5, "MD5")
}

@ -0,0 +1,10 @@
package wxworkbot
type markdownMessage struct {
message
Markdown Markdown `json:"markdown"`
}
type Markdown struct {
Content string `json:"content"`
}

@ -0,0 +1,23 @@
package wxworkbot
import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
)
func TestMarkdownMessage(t *testing.T) {
jsonString := `
{
"msgtype": "markdown",
"markdown": {
"content": "<font color=\"warning\">233</font>"
}
}`
var markdownMsg markdownMessage
err := json.Unmarshal([]byte(jsonString), &markdownMsg)
assert.Nil(t, err)
assert.Equal(t, markdownMsg.MsgType, "markdown")
assert.Equal(t, markdownMsg.Markdown.Content,
"<font color=\"warning\">233</font>")
}

@ -0,0 +1,5 @@
package wxworkbot
type message struct {
MsgType string `json:"msgtype"`
}

@ -0,0 +1,17 @@
package wxworkbot
type newsMessage struct {
message
News News `json:"news"`
}
type News struct {
Articles []NewsArticle `json:"articles"`
}
type NewsArticle struct {
Title string `json:"title"`
Description string `json:"description"`
URL string `json:"url"`
PicURL string `json:"picurl"`
}

@ -0,0 +1,35 @@
package wxworkbot
import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
)
func TestNewsMessage(t *testing.T) {
jsonString := `
{
"msgtype": "news",
"news": {
"articles" : [
{
"title" : "中秋节礼品领取",
"description" : "今年中秋节公司有豪礼相送",
"url" : "URL",
"picurl" : "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png"
}
]
}
}
`
var newsMsg newsMessage
err := json.Unmarshal([]byte(jsonString), &newsMsg)
assert.Nil(t, err)
assert.Equal(t, newsMsg.MsgType, "news")
assert.NotEmpty(t, newsMsg.News.Articles)
article := newsMsg.News.Articles[0]
assert.Equal(t, article.Title, "中秋节礼品领取")
assert.Equal(t, article.Description, "今年中秋节公司有豪礼相送")
assert.Equal(t, article.URL, "URL")
assert.Equal(t, article.PicURL, "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png")
}

@ -0,0 +1,12 @@
package wxworkbot
type textMessage struct {
message
Text Text `json:"text"`
}
type Text struct {
Content string `json:"content"`
MentionedList []string `json:"mentioned_list"`
MentionedMobileList []string `json:"mentioned_mobile_list"`
}

@ -0,0 +1,26 @@
package wxworkbot
import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
)
func TestTextMessage(t *testing.T) {
jsonString := `
{
"msgtype": "text",
"text": {
"content": "广州今日天气29度大部分多云降雨概率60%",
"mentioned_list":["wangqing","@all"],
"mentioned_mobile_list":["13800001111","@all"]
}
}`
var textMsg textMessage
err := json.Unmarshal([]byte(jsonString), &textMsg)
assert.Nil(t, err)
assert.Equal(t, textMsg.MsgType, "text")
assert.Equal(t, textMsg.Text.Content, "广州今日天气29度大部分多云降雨概率60%")
assert.Equal(t, textMsg.Text.MentionedList, []string{"wangqing", "@all"})
assert.Equal(t, textMsg.Text.MentionedMobileList, []string{"13800001111", "@all"})
}

@ -0,0 +1,6 @@
package wxworkbot
type wxWorkResponse struct {
ErrorCode int `json:"errcode"`
ErrorMessage string `json:"errmsg"`
}
Loading…
Cancel
Save