mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-11-11 03:33:53 +08:00
v2.0
This commit is contained in:
83
server/internal/library/notify/feishu/client.go
Normal file
83
server/internal/library/notify/feishu/client.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package feishu
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"hotgo/internal/library/notify/feishu/internal/security"
|
||||
)
|
||||
|
||||
const feishuAPI = "https://open.feishu.cn/open-apis/bot/v2/hook/"
|
||||
|
||||
// Client feishu client
|
||||
type Client struct {
|
||||
AccessToken string
|
||||
Secret string
|
||||
}
|
||||
|
||||
// NewClient new client
|
||||
func NewClient(accessToken, secret string) *Client {
|
||||
return &Client{
|
||||
AccessToken: accessToken,
|
||||
Secret: secret,
|
||||
}
|
||||
}
|
||||
|
||||
// Response response struct
|
||||
type Response struct {
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data interface{} `json:"data"`
|
||||
|
||||
Extra interface{} `json:"Extra"`
|
||||
StatusCode int64 `json:"StatusCode"`
|
||||
StatusMessage string `json:"StatusMessage"`
|
||||
}
|
||||
|
||||
// Send send message
|
||||
func (d *Client) Send(message Message) (string, *Response, error) {
|
||||
res := &Response{}
|
||||
|
||||
if len(d.AccessToken) < 1 {
|
||||
return "", res, fmt.Errorf("accessToken is empty")
|
||||
}
|
||||
|
||||
timestamp := time.Now().Unix()
|
||||
sign, err := security.GenSign(d.Secret, timestamp)
|
||||
if err != nil {
|
||||
return "", res, err
|
||||
}
|
||||
|
||||
body := message.Body()
|
||||
body["timestamp"] = strconv.FormatInt(timestamp, 10)
|
||||
body["sign"] = sign
|
||||
|
||||
reqBytes, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return "", res, err
|
||||
}
|
||||
reqString := string(reqBytes)
|
||||
|
||||
client := resty.New()
|
||||
URL := fmt.Sprintf("%v%v", feishuAPI, d.AccessToken)
|
||||
resp, err := client.SetRetryCount(3).R().
|
||||
SetBody(body).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetResult(&Response{}).
|
||||
ForceContentType("application/json").
|
||||
Post(URL)
|
||||
|
||||
if err != nil {
|
||||
return reqString, nil, err
|
||||
}
|
||||
|
||||
result := resp.Result().(*Response)
|
||||
if result.Code != 0 {
|
||||
return reqString, result, fmt.Errorf("send message to feishu error = %s", result.Msg)
|
||||
}
|
||||
return reqString, result, nil
|
||||
}
|
||||
24
server/internal/library/notify/feishu/image.go
Normal file
24
server/internal/library/notify/feishu/image.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package feishu
|
||||
|
||||
type ImageMessage struct {
|
||||
MsgType MsgType `json:"msg_type"`
|
||||
Content ImageContent `json:"content"`
|
||||
}
|
||||
|
||||
type ImageContent struct {
|
||||
ImageKey string `json:"image_key"`
|
||||
}
|
||||
|
||||
func (m *ImageMessage) Body() map[string]interface{} {
|
||||
m.MsgType = MsgTypeImage
|
||||
return structToMap(m)
|
||||
}
|
||||
|
||||
func NewImageMessage() *ImageMessage {
|
||||
return &ImageMessage{}
|
||||
}
|
||||
|
||||
func (m *ImageMessage) SetImageKey(key string) *ImageMessage {
|
||||
m.Content.ImageKey = key
|
||||
return m
|
||||
}
|
||||
21
server/internal/library/notify/feishu/interactive.go
Normal file
21
server/internal/library/notify/feishu/interactive.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package feishu
|
||||
|
||||
type InteractiveMessage struct {
|
||||
MsgType MsgType `json:"msg_type"`
|
||||
Card string `json:"card"`
|
||||
}
|
||||
|
||||
func (m *InteractiveMessage) Body() map[string]interface{} {
|
||||
m.MsgType = MsgTypeInteractive
|
||||
return structToMap(m)
|
||||
}
|
||||
|
||||
func NewInteractiveMessage() *InteractiveMessage {
|
||||
return &InteractiveMessage{}
|
||||
}
|
||||
|
||||
// SetCard set card with cardbuilder https://open.feishu.cn/tool/cardbuilder?from=custom_bot_doc
|
||||
func (m *InteractiveMessage) SetCard(card string) *InteractiveMessage {
|
||||
m.Card = card
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// GenSign generate sign
|
||||
func GenSign(secret string, timestamp int64) (string, error) {
|
||||
stringToSign := fmt.Sprintf("%v", timestamp) + "\n" + secret
|
||||
var data []byte
|
||||
h := hmac.New(sha256.New, []byte(stringToSign))
|
||||
_, err := h.Write(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
return signature, nil
|
||||
}
|
||||
66
server/internal/library/notify/feishu/message.go
Normal file
66
server/internal/library/notify/feishu/message.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package feishu
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MsgType string
|
||||
|
||||
// MsgType
|
||||
const (
|
||||
MsgTypeText MsgType = "text"
|
||||
MsgTypePost MsgType = "post"
|
||||
MsgTypeImage MsgType = "image"
|
||||
MsgTypeShareChat MsgType = "share_chat"
|
||||
MsgTypeInteractive MsgType = "interactive"
|
||||
)
|
||||
|
||||
// Message interface
|
||||
type Message interface {
|
||||
Body() map[string]interface{}
|
||||
}
|
||||
|
||||
func structToMap(item interface{}) map[string]interface{} {
|
||||
res := map[string]interface{}{}
|
||||
if item == nil {
|
||||
return res
|
||||
}
|
||||
v := reflect.TypeOf(item)
|
||||
reflectValue := reflect.ValueOf(item)
|
||||
reflectValue = reflect.Indirect(reflectValue)
|
||||
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
tag := v.Field(i).Tag.Get("json")
|
||||
|
||||
// remove omitEmpty
|
||||
omitEmpty := false
|
||||
if strings.HasSuffix(tag, "omitempty") {
|
||||
omitEmpty = true
|
||||
idx := strings.Index(tag, ",")
|
||||
if idx > 0 {
|
||||
tag = tag[:idx]
|
||||
} else {
|
||||
tag = ""
|
||||
}
|
||||
}
|
||||
|
||||
field := reflectValue.Field(i).Interface()
|
||||
if tag != "" && tag != "-" {
|
||||
if omitEmpty && reflectValue.Field(i).IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
if v.Field(i).Type.Kind() == reflect.Struct {
|
||||
res[tag] = structToMap(field)
|
||||
} else {
|
||||
res[tag] = field
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
170
server/internal/library/notify/feishu/post.go
Normal file
170
server/internal/library/notify/feishu/post.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package feishu
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
)
|
||||
|
||||
type PostMessage struct {
|
||||
MsgType MsgType `json:"msg_type"`
|
||||
Content PostContent `json:"content"`
|
||||
}
|
||||
|
||||
func NewPostMessage() *PostMessage {
|
||||
return &PostMessage{}
|
||||
}
|
||||
|
||||
func (m *PostMessage) Body() map[string]interface{} {
|
||||
m.MsgType = MsgTypePost
|
||||
return structToMap(m)
|
||||
}
|
||||
|
||||
func (m *PostMessage) SetZH(u PostUnit) *PostMessage {
|
||||
m.Content.Post.ZH = u
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) SetZHTitle(t string) *PostMessage {
|
||||
m.Content.Post.ZH.Title = t
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) AppendZHContent(i []PostItem) *PostMessage {
|
||||
m.Content.Post.ZH.Content = append(m.Content.Post.ZH.Content, i)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) SetJA(u PostUnit) *PostMessage {
|
||||
m.Content.Post.JA = u
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) SetJATitle(t string) *PostMessage {
|
||||
m.Content.Post.JA.Title = t
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) AppendJAContent(i []PostItem) *PostMessage {
|
||||
m.Content.Post.JA.Content = append(m.Content.Post.JA.Content, i)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) SetEN(u PostUnit) *PostMessage {
|
||||
m.Content.Post.EN = u
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) SetENTitle(t string) *PostMessage {
|
||||
m.Content.Post.EN.Title = t
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) AppendENContent(i []PostItem) *PostMessage {
|
||||
m.Content.Post.EN.Content = append(m.Content.Post.EN.Content, i)
|
||||
return m
|
||||
}
|
||||
|
||||
type PostContent struct {
|
||||
Post PostBody `json:"post"`
|
||||
}
|
||||
|
||||
type PostBody struct {
|
||||
ZH PostUnit `json:"zh_cn,omitempty"`
|
||||
JA PostUnit `json:"ja_jp,omitempty"`
|
||||
EN PostUnit `json:"en_us,omitempty"`
|
||||
}
|
||||
|
||||
type PostUnit struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Content [][]PostItem `json:"content"`
|
||||
}
|
||||
|
||||
type PostItem interface{}
|
||||
|
||||
type Text struct {
|
||||
Tag string `json:"tag"`
|
||||
Text string `json:"text"`
|
||||
UnEscape bool `json:"un_escape,omitempty"`
|
||||
}
|
||||
|
||||
func NewText(text string) Text {
|
||||
t := Text{
|
||||
Tag: "text",
|
||||
Text: text,
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
type A struct {
|
||||
Tag string `json:"tag"`
|
||||
Text string `json:"text"`
|
||||
Href string `json:"href"`
|
||||
UnEscape bool `json:"un_escape,omitempty"`
|
||||
}
|
||||
|
||||
func NewA(text, href string) A {
|
||||
t := A{
|
||||
Tag: "a",
|
||||
Text: text,
|
||||
Href: href,
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
type AT struct {
|
||||
Tag string `json:"tag"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
func NewAT(userID string) AT {
|
||||
t := AT{
|
||||
Tag: "at",
|
||||
UserID: userID,
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
Tag string `json:"tag"`
|
||||
ImageKey string `json:"image_key"`
|
||||
Height int `json:"height"`
|
||||
Width int `json:"width"`
|
||||
}
|
||||
|
||||
func NewImage(imageKey string, height, width int) Image {
|
||||
t := Image{
|
||||
Tag: "image",
|
||||
ImageKey: imageKey,
|
||||
Height: height,
|
||||
Width: width,
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
type PostCMDMessage struct {
|
||||
MsgType MsgType `json:"msg_type"`
|
||||
Content PostCMDContent `json:"content"`
|
||||
}
|
||||
|
||||
func (m *PostCMDMessage) Body() map[string]interface{} {
|
||||
m.MsgType = MsgTypePost
|
||||
return structToMap(m)
|
||||
}
|
||||
|
||||
type PostCMDContent struct {
|
||||
Post map[string]interface{} `json:"post"`
|
||||
}
|
||||
|
||||
func NewPostCMDMessage() *PostCMDMessage {
|
||||
return &PostCMDMessage{}
|
||||
}
|
||||
|
||||
func (m *PostCMDMessage) SetPost(post string) *PostCMDMessage {
|
||||
var result map[string]interface{}
|
||||
err := json.Unmarshal([]byte(post), &result)
|
||||
if err != nil {
|
||||
log.Print("SetPost err: ", err)
|
||||
}
|
||||
m.Content.Post = result
|
||||
return m
|
||||
}
|
||||
24
server/internal/library/notify/feishu/sharechat.go
Normal file
24
server/internal/library/notify/feishu/sharechat.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package feishu
|
||||
|
||||
type ShareChatMessage struct {
|
||||
MsgType MsgType `json:"msg_type"`
|
||||
Content ShareChatContent `json:"content"`
|
||||
}
|
||||
|
||||
type ShareChatContent struct {
|
||||
ShareChatID string `json:"share_chat_id"`
|
||||
}
|
||||
|
||||
func (m *ShareChatMessage) Body() map[string]interface{} {
|
||||
m.MsgType = MsgTypeShareChat
|
||||
return structToMap(m)
|
||||
}
|
||||
|
||||
func NewShareChatMessage() *ShareChatMessage {
|
||||
return &ShareChatMessage{}
|
||||
}
|
||||
|
||||
func (m *ShareChatMessage) SetShareChatID(ID string) *ShareChatMessage {
|
||||
m.Content.ShareChatID = ID
|
||||
return m
|
||||
}
|
||||
24
server/internal/library/notify/feishu/text.go
Normal file
24
server/internal/library/notify/feishu/text.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package feishu
|
||||
|
||||
type TextMessage struct {
|
||||
MsgType MsgType `json:"msg_type"`
|
||||
Content Content `json:"content"`
|
||||
}
|
||||
|
||||
type Content struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
func (m *TextMessage) Body() map[string]interface{} {
|
||||
m.MsgType = MsgTypeText
|
||||
return structToMap(m)
|
||||
}
|
||||
|
||||
func NewTextMessage() *TextMessage {
|
||||
return &TextMessage{}
|
||||
}
|
||||
|
||||
func (m *TextMessage) SetText(text string) *TextMessage {
|
||||
m.Content.Text = text
|
||||
return m
|
||||
}
|
||||
Reference in New Issue
Block a user