refactor: reimplement dingtalkbot notifier, drop thrird-party deps

This commit is contained in:
Fu Diwei 2025-10-30 20:18:14 +08:00
parent 0ec3596745
commit 1ae61549a1

View File

@ -2,12 +2,18 @@ package dingtalkbot
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"net/url"
"time"
"github.com/blinkbean/dingtalk"
"github.com/go-resty/resty/v2"
"github.com/certimate-go/certimate/pkg/core"
)
@ -20,8 +26,9 @@ type NotifierProviderConfig struct {
}
type NotifierProvider struct {
config *NotifierProviderConfig
logger *slog.Logger
config *NotifierProviderConfig
logger *slog.Logger
httpClient *resty.Client
}
var _ core.Notifier = (*NotifierProvider)(nil)
@ -31,9 +38,30 @@ func NewNotifierProvider(config *NotifierProviderConfig) (*NotifierProvider, err
return nil, errors.New("the configuration of the notifier provider is nil")
}
client := resty.New().
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", "certimate").
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
if config.Secret != "" {
timestamp := fmt.Sprintf("%d", time.Now().UnixMilli())
h := hmac.New(sha256.New, []byte(config.Secret))
h.Write([]byte(fmt.Sprintf("%s\n%s", timestamp, config.Secret)))
sign := base64.StdEncoding.EncodeToString(h.Sum(nil))
qs := req.URL.Query()
qs.Set("timestamp", timestamp)
qs.Set("sign", sign)
req.URL.RawQuery = qs.Encode()
}
return nil
})
return &NotifierProvider{
config: config,
logger: slog.Default(),
config: config,
logger: slog.Default(),
httpClient: client,
}, nil
}
@ -49,17 +77,35 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s
webhookUrl, err := url.Parse(n.config.WebhookUrl)
if err != nil {
return nil, fmt.Errorf("dingtalk api error: invalid webhook url: %w", err)
}
var bot *dingtalk.DingTalk
if n.config.Secret == "" {
bot = dingtalk.InitDingTalk([]string{webhookUrl.Query().Get("access_token")}, "")
} else {
bot = dingtalk.InitDingTalkWithSecret(webhookUrl.Query().Get("access_token"), n.config.Secret)
const hostname = "oapi.dingtalk.com"
if webhookUrl.Hostname() != hostname {
n.logger.Warn(fmt.Sprintf("the webhook url hostname is not '%s', please make sure it is correct", hostname))
}
}
if err := bot.SendTextMessage(subject + "\n" + message); err != nil {
return nil, fmt.Errorf("dingtalk api error: %w", err)
// REF: https://open.dingtalk.com/document/development/custom-robots-send-group-messages
var result struct {
ErrorCode int `json:"errcode"`
ErrorMessage string `json:"errmsg"`
}
req := n.httpClient.R().
SetContext(ctx).
SetBody(map[string]any{
"msgtype": "text",
"text": map[string]string{
"content": subject + "\n\n" + message,
},
})
resp, err := req.Post(webhookUrl.String())
if err != nil {
return nil, fmt.Errorf("dingtalk api error: failed to send request: %w", err)
} else if resp.IsError() {
return nil, fmt.Errorf("dingtalk api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
} else if err := json.Unmarshal(resp.Body(), &result); err != nil {
return nil, fmt.Errorf("dingtalk api error: failed to unmarshal response: %w", err)
} else if result.ErrorCode != 0 {
return nil, fmt.Errorf("dingtalk api error: errcode='%d', errmsg='%s'", result.ErrorCode, result.ErrorMessage)
}
return &core.NotifyResult{}, nil