From ec0cdf8b96f52e99152d9ad17d2891e587dbe36b Mon Sep 17 00:00:00 2001
From: banto <13196831+banto6@users.noreply.github.com>
Date: Fri, 11 Apr 2025 22:55:47 +0800
Subject: [PATCH] feat(notify): add mattermost
---
internal/domain/notify.go | 1 +
internal/notify/providers.go | 9 ++
.../providers/mattermost/mattermost.go | 89 +++++++++++++++++++
.../providers/mattermost/mattermost_test.go | 74 +++++++++++++++
.../notification/NotifyChannelEditForm.tsx | 11 ++-
.../NotifyChannelEditFormMattermostFields.tsx | 63 +++++++++++++
ui/src/domain/settings.ts | 11 +++
ui/src/i18n/locales/en/nls.common.json | 1 +
ui/src/i18n/locales/en/nls.settings.json | 9 ++
ui/src/i18n/locales/zh/nls.common.json | 1 +
ui/src/i18n/locales/zh/nls.settings.json | 9 ++
11 files changed, 274 insertions(+), 4 deletions(-)
create mode 100644 internal/pkg/core/notifier/providers/mattermost/mattermost.go
create mode 100644 internal/pkg/core/notifier/providers/mattermost/mattermost_test.go
create mode 100644 ui/src/components/notification/NotifyChannelEditFormMattermostFields.tsx
diff --git a/internal/domain/notify.go b/internal/domain/notify.go
index 4bc57b85..06c8bbae 100644
--- a/internal/domain/notify.go
+++ b/internal/domain/notify.go
@@ -14,6 +14,7 @@ const (
NotifyChannelTypeEmail = NotifyChannelType("email")
NotifyChannelTypeGotify = NotifyChannelType("gotify")
NotifyChannelTypeLark = NotifyChannelType("lark")
+ NotifyChannelTypeMattermost = NotifyChannelType("mattermost")
NotifyChannelTypePushPlus = NotifyChannelType("pushplus")
NotifyChannelTypeServerChan = NotifyChannelType("serverchan")
NotifyChannelTypeTelegram = NotifyChannelType("telegram")
diff --git a/internal/notify/providers.go b/internal/notify/providers.go
index 3a7cadf9..6e5a6aef 100644
--- a/internal/notify/providers.go
+++ b/internal/notify/providers.go
@@ -10,6 +10,7 @@ import (
pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
pGotify "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/gotify"
pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark"
+ pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost"
pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus"
pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan"
pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram"
@@ -59,6 +60,14 @@ func createNotifier(channel domain.NotifyChannelType, channelConfig map[string]a
WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"),
})
+ case domain.NotifyChannelTypeMattermost:
+ return pMattermost.NewNotifier(&pMattermost.NotifierConfig{
+ ServerUrl: maputil.GetString(channelConfig, "serverUrl"),
+ ChannelId: maputil.GetString(channelConfig, "channelId"),
+ Username: maputil.GetString(channelConfig, "username"),
+ Password: maputil.GetString(channelConfig, "password"),
+ })
+
case domain.NotifyChannelTypePushPlus:
return pPushPlus.NewNotifier(&pPushPlus.NotifierConfig{
Token: maputil.GetString(channelConfig, "token"),
diff --git a/internal/pkg/core/notifier/providers/mattermost/mattermost.go b/internal/pkg/core/notifier/providers/mattermost/mattermost.go
new file mode 100644
index 00000000..24890794
--- /dev/null
+++ b/internal/pkg/core/notifier/providers/mattermost/mattermost.go
@@ -0,0 +1,89 @@
+package mattermost
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "github.com/nikoksr/notify/service/mattermost"
+ "github.com/usual2970/certimate/internal/pkg/core/notifier"
+ "io"
+ "log/slog"
+ "net/http"
+)
+
+type NotifierConfig struct {
+ // Mattermost 服务地址。
+ ServerUrl string `json:"serverUrl"`
+ // 频道ID
+ ChannelId string `json:"channelId"`
+ // 用户名
+ Username string `json:"username"`
+ // 密码
+ Password string `json:"password"`
+}
+
+type NotifierProvider struct {
+ config *NotifierConfig
+ logger *slog.Logger
+}
+
+var _ notifier.Notifier = (*NotifierProvider)(nil)
+
+func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
+ if config == nil {
+ panic("config is nil")
+ }
+
+ return &NotifierProvider{
+ config: config,
+ }, nil
+}
+
+func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
+ if logger == nil {
+ n.logger = slog.Default()
+ } else {
+ n.logger = logger
+ }
+ return n
+}
+
+func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
+ srv := mattermost.New(n.config.ServerUrl)
+
+ if err := srv.LoginWithCredentials(ctx, n.config.Username, n.config.Password); err != nil {
+ return nil, err
+ }
+
+ srv.AddReceivers(n.config.ChannelId)
+
+ // 复写消息样式
+ srv.PreSend(func(req *http.Request) error {
+ m := map[string]interface{}{
+ "channel_id": n.config.ChannelId,
+ "props": map[string]interface{}{
+ "attachments": []map[string]interface{}{
+ {
+ "title": subject,
+ "text": message,
+ },
+ },
+ },
+ }
+
+ if body, err := json.Marshal(m); err != nil {
+ return err
+ } else {
+ req.ContentLength = int64(len(body))
+ req.Body = io.NopCloser(bytes.NewReader(body))
+ }
+
+ return nil
+ })
+
+ if err = srv.Send(ctx, subject, message); err != nil {
+ return nil, err
+ }
+
+ return ¬ifier.NotifyResult{}, nil
+}
diff --git a/internal/pkg/core/notifier/providers/mattermost/mattermost_test.go b/internal/pkg/core/notifier/providers/mattermost/mattermost_test.go
new file mode 100644
index 00000000..6db6cc42
--- /dev/null
+++ b/internal/pkg/core/notifier/providers/mattermost/mattermost_test.go
@@ -0,0 +1,74 @@
+package mattermost_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "strings"
+ "testing"
+
+ provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost"
+)
+
+const (
+ mockSubject = "test_subject"
+ mockMessage = "test_message"
+)
+
+var (
+ fServerUrl string
+ fChannelId string
+ fUsername string
+ fPassword string
+)
+
+func init() {
+ argsPrefix := "CERTIMATE_NOTIFIER_MATTERMOST_"
+
+ flag.StringVar(&fServerUrl, argsPrefix+"SERVERURL", "", "")
+ flag.StringVar(&fChannelId, argsPrefix+"CHANNELID", "", "")
+ flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "")
+ flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "")
+}
+
+/*
+Shell command to run this test:
+
+ go test -v ./mattermost_test.go -args \
+ --CERTIMATE_NOTIFIER_MATTERMOST_SERVERURL="https://example.com/your-server-url" \
+ --CERTIMATE_NOTIFIER_MATTERMOST_CHANNELID="your-chanel-id" \
+ --CERTIMATE_NOTIFIER_MATTERMOST_USERNAME="your-username" \
+ --CERTIMATE_NOTIFIER_MATTERMOST_PASSWORD="your-password"
+*/
+func TestNotify(t *testing.T) {
+ flag.Parse()
+
+ t.Run("Notify", func(t *testing.T) {
+ t.Log(strings.Join([]string{
+ "args:",
+ fmt.Sprintf("SERVERURL: %v", fServerUrl),
+ fmt.Sprintf("CHANNELID: %v", fChannelId),
+ fmt.Sprintf("USERNAME: %v", fUsername),
+ fmt.Sprintf("PASSWORD: %v", fPassword),
+ }, "\n"))
+
+ notifier, err := provider.NewNotifier(&provider.NotifierConfig{
+ ServerUrl: fServerUrl,
+ ChannelId: fChannelId,
+ Username: fUsername,
+ Password: fPassword,
+ })
+ if err != nil {
+ t.Errorf("err: %+v", err)
+ return
+ }
+
+ res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
+ if err != nil {
+ t.Errorf("err: %+v", err)
+ return
+ }
+
+ t.Logf("ok: %v", res)
+ })
+}
diff --git a/ui/src/components/notification/NotifyChannelEditForm.tsx b/ui/src/components/notification/NotifyChannelEditForm.tsx
index aa3f4f12..fb87c5a8 100644
--- a/ui/src/components/notification/NotifyChannelEditForm.tsx
+++ b/ui/src/components/notification/NotifyChannelEditForm.tsx
@@ -1,8 +1,8 @@
-import { forwardRef, useImperativeHandle, useMemo } from "react";
-import { Form, type FormInstance } from "antd";
+import {forwardRef, useImperativeHandle, useMemo} from "react";
+import {Form, type FormInstance} from "antd";
-import { NOTIFY_CHANNELS, type NotifyChannelsSettingsContent } from "@/domain/settings";
-import { useAntdForm } from "@/hooks";
+import {NOTIFY_CHANNELS, type NotifyChannelsSettingsContent} from "@/domain/settings";
+import {useAntdForm} from "@/hooks";
import NotifyChannelEditFormBarkFields from "./NotifyChannelEditFormBarkFields";
import NotifyChannelEditFormDingTalkFields from "./NotifyChannelEditFormDingTalkFields";
@@ -14,6 +14,7 @@ import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServer
import NotifyChannelEditFormTelegramFields from "./NotifyChannelEditFormTelegramFields";
import NotifyChannelEditFormWebhookFields from "./NotifyChannelEditFormWebhookFields";
import NotifyChannelEditFormWeComFields from "./NotifyChannelEditFormWeComFields";
+import NotifyChannelEditFormMattermostFields from "@/components/notification/NotifyChannelEditFormMattermostFields.tsx";
type NotifyChannelEditFormFieldValues = NotifyChannelsSettingsContent[keyof NotifyChannelsSettingsContent];
@@ -54,6 +55,8 @@ const NotifyChannelEditForm = forwardRef