feat: 完成一般消息通知能力的开发
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
package notice
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type feishuRenderer struct{}
|
||||
|
||||
func (feishuRenderer) Capabilities() Capabilities {
|
||||
return Capabilities{Text: true, ColorText: true, Markdown: true, Card: true}
|
||||
}
|
||||
|
||||
func (feishuRenderer) Render(config Config, message Message) (renderedRequest, error) {
|
||||
body := map[string]any{}
|
||||
switch message.Type {
|
||||
case MessageText:
|
||||
body["msg_type"] = "text"
|
||||
body["content"] = map[string]any{"text": message.Content}
|
||||
case MessageColorText:
|
||||
body["msg_type"] = "interactive"
|
||||
body["card"] = feishuCard("", CardThemeDefault, feishuColorMarkdown(message.Segments), nil, nil)
|
||||
case MessageMarkdown:
|
||||
body["msg_type"] = "interactive"
|
||||
body["card"] = feishuCard(message.Title, CardThemeDefault, message.Content, nil, nil)
|
||||
case MessageCard:
|
||||
body["msg_type"] = "interactive"
|
||||
body["card"] = feishuCard(message.Card.Title, message.Card.Theme, message.Card.Markdown, message.Card.Fields, message.Card.Actions)
|
||||
}
|
||||
|
||||
secret := signatureSecret(config)
|
||||
if secret != "" {
|
||||
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
body["timestamp"] = timestamp
|
||||
body["sign"] = feishuSign(timestamp, secret)
|
||||
}
|
||||
return marshalRequest(config.Webhook, body)
|
||||
}
|
||||
|
||||
func (feishuRenderer) ValidateResponse(response networkResponse) error {
|
||||
if err := validateHTTP(ChannelFeishu, response); err != nil {
|
||||
return err
|
||||
}
|
||||
code, message, err := responseStatus(response.Body, "code", "StatusCode", "msg", "StatusMessage")
|
||||
if err != nil {
|
||||
return fmt.Errorf("notice: parse feishu response: %w", err)
|
||||
}
|
||||
if code != "0" {
|
||||
return &PlatformError{Channel: ChannelFeishu, Code: code, Message: message}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func feishuSign(timestamp, secret string) string {
|
||||
key := []byte(timestamp + "\n" + secret)
|
||||
digest := hmac.New(sha256.New, key)
|
||||
return base64.StdEncoding.EncodeToString(digest.Sum(nil))
|
||||
}
|
||||
|
||||
func feishuColorMarkdown(segments []TextSegment) string {
|
||||
var builder strings.Builder
|
||||
for _, segment := range segments {
|
||||
content := escapeText(segment.Text)
|
||||
if segment.Bold {
|
||||
content = "**" + content + "**"
|
||||
}
|
||||
color := map[Color]string{
|
||||
ColorInfo: "blue", ColorSuccess: "green", ColorWarning: "orange",
|
||||
ColorDanger: "red", ColorMuted: "grey",
|
||||
}[segment.Color]
|
||||
if color != "" {
|
||||
content = `<font color='` + color + `'>` + content + `</font>`
|
||||
}
|
||||
builder.WriteString(content)
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func feishuCard(title string, theme CardTheme, markdown string, fields []CardField, actions []CardAction) map[string]any {
|
||||
if theme == "" {
|
||||
theme = CardThemeDefault
|
||||
}
|
||||
elements := make([]any, 0, 2)
|
||||
content := strings.TrimSpace(markdown)
|
||||
if len(fields) > 0 {
|
||||
content = strings.TrimSpace(cardMarkdown(&CardContent{Markdown: markdown, Fields: fields}))
|
||||
}
|
||||
if content != "" {
|
||||
elements = append(elements, map[string]any{"tag": "markdown", "content": content})
|
||||
}
|
||||
if len(actions) > 0 {
|
||||
for _, action := range actions {
|
||||
elements = append(elements, map[string]any{
|
||||
"tag": "button",
|
||||
"text": map[string]any{"tag": "plain_text", "content": action.Text},
|
||||
"type": "default",
|
||||
"behaviors": []any{map[string]any{"type": "open_url", "default_url": action.URL}},
|
||||
})
|
||||
}
|
||||
}
|
||||
card := map[string]any{
|
||||
"schema": "2.0",
|
||||
"body": map[string]any{"direction": "vertical", "elements": elements},
|
||||
}
|
||||
if strings.TrimSpace(title) == "" && theme != CardThemeDefault {
|
||||
title = "消息通知"
|
||||
}
|
||||
if strings.TrimSpace(title) != "" {
|
||||
card["header"] = map[string]any{
|
||||
"title": map[string]any{"tag": "plain_text", "content": title},
|
||||
"template": string(theme),
|
||||
}
|
||||
}
|
||||
return card
|
||||
}
|
||||
|
||||
func responseStatus(body []byte, codeKey, alternateCodeKey, messageKey, alternateMessageKey string) (string, string, error) {
|
||||
var payload map[string]json.RawMessage
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
codeRaw := payload[codeKey]
|
||||
if len(codeRaw) == 0 {
|
||||
codeRaw = payload[alternateCodeKey]
|
||||
}
|
||||
if len(codeRaw) == 0 {
|
||||
return "", "", fmt.Errorf("business code is missing")
|
||||
}
|
||||
var code any
|
||||
if err := json.Unmarshal(codeRaw, &code); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
messageRaw := payload[messageKey]
|
||||
if len(messageRaw) == 0 {
|
||||
messageRaw = payload[alternateMessageKey]
|
||||
}
|
||||
var message string
|
||||
_ = json.Unmarshal(messageRaw, &message)
|
||||
return fmt.Sprint(code), message, nil
|
||||
}
|
||||
Reference in New Issue
Block a user