Merge pull request #604 from banto6/main

feat(notify): add mattermost
This commit is contained in:
Yoan.liu 2025-04-13 08:24:26 +08:00 committed by GitHub
commit 0f1d5a7730
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 272 additions and 0 deletions

View File

@ -14,6 +14,7 @@ const (
NotifyChannelTypeEmail = NotifyChannelType("email")
NotifyChannelTypeGotify = NotifyChannelType("gotify")
NotifyChannelTypeLark = NotifyChannelType("lark")
NotifyChannelTypeMattermost = NotifyChannelType("mattermost")
NotifyChannelTypePushover = NotifyChannelType("pushover")
NotifyChannelTypePushPlus = NotifyChannelType("pushplus")
NotifyChannelTypeServerChan = NotifyChannelType("serverchan")

View File

@ -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"
pPushover "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushover"
pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus"
pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan"
@ -60,6 +61,13 @@ 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.NotifyChannelTypePushover:
return pPushover.NewNotifier(&pPushover.NotifierConfig{
Token: maputil.GetString(channelConfig, "token"),

View File

@ -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 &notifier.NotifyResult{}, nil
}

View File

@ -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)
})
}

View File

@ -9,6 +9,7 @@ import NotifyChannelEditFormDingTalkFields from "./NotifyChannelEditFormDingTalk
import NotifyChannelEditFormEmailFields from "./NotifyChannelEditFormEmailFields";
import NotifyChannelEditFormGotifyFields from "./NotifyChannelEditFormGotifyFields.tsx";
import NotifyChannelEditFormLarkFields from "./NotifyChannelEditFormLarkFields";
import NotifyChannelEditFormMattermostFields from "./NotifyChannelEditFormMattermostFields.tsx";
import NotifyChannelEditFormPushoverFields from "./NotifyChannelEditFormPushoverFields";
import NotifyChannelEditFormPushPlusFields from "./NotifyChannelEditFormPushPlusFields";
import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServerChanFields";
@ -55,6 +56,8 @@ const NotifyChannelEditForm = forwardRef<NotifyChannelEditFormInstance, NotifyCh
return <NotifyChannelEditFormGotifyFields />;
case NOTIFY_CHANNELS.LARK:
return <NotifyChannelEditFormLarkFields />;
case NOTIFY_CHANNELS.MATTERMOST:
return <NotifyChannelEditFormMattermostFields />;
case NOTIFY_CHANNELS.PUSHOVER:
return <NotifyChannelEditFormPushoverFields />;
case NOTIFY_CHANNELS.PUSHPLUS:

View File

@ -0,0 +1,64 @@
import { useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
const NotifyChannelEditFormMattermostFields = () => {
const { t } = useTranslation();
const formSchema = z.object({
serverUrl: z
.string({ message: t("settings.notification.channel.form.mattermost_server_url.placeholder") })
.url(t("common.errmsg.url_invalid")),
channelId: z
.string({ message: t("settings.notification.channel.form.mattermost_channel_id.placeholder") })
.nonempty(t("settings.notification.channel.form.mattermost_channel_id.placeholder")),
username: z
.string({ message: t("settings.notification.channel.form.mattermost_username.placeholder") })
.nonempty(t("settings.notification.channel.form.mattermost_username.placeholder")),
password: z
.string({ message: t("settings.notification.channel.form.mattermost_password.placeholder") })
.nonempty(t("settings.notification.channel.form.mattermost_password.placeholder")),
});
const formRule = createSchemaFieldRule(formSchema);
return (
<>
<Form.Item
name="serverUrl"
label={t("settings.notification.channel.form.mattermost_server_url.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.notification.channel.form.mattermost_server_url.tooltip") }}></span>}
>
<Input placeholder={t("settings.notification.channel.form.mattermost_server_url.placeholder")} />
</Form.Item>
<Form.Item
name="channelId"
label={t("settings.notification.channel.form.mattermost_channel_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.notification.channel.form.mattermost_channel_id.tooltip") }}></span>}
>
<Input placeholder={t("settings.notification.channel.form.mattermost_channel_id.placeholder")} />
</Form.Item>
<Form.Item
name="username"
label={t("settings.notification.channel.form.mattermost_username.label")}
rules={[formRule]}
>
<Input placeholder={t("settings.notification.channel.form.mattermost_username.placeholder")} />
</Form.Item>
<Form.Item
name="password"
label={t("settings.notification.channel.form.mattermost_password.label")}
rules={[formRule]}
>
<Input.Password placeholder={t("settings.notification.channel.form.mattermost_password.placeholder")} />
</Form.Item>
</>
);
};
export default NotifyChannelEditFormMattermostFields;

View File

@ -44,6 +44,7 @@ export const NOTIFY_CHANNELS = Object.freeze({
EMAIL: "email",
GOTIFY: "gotify",
LARK: "lark",
MATTERMOST: "mattermost",
PUSHOVER: "pushover",
PUSHPLUS: "pushplus",
SERVERCHAN: "serverchan",
@ -65,6 +66,7 @@ export type NotifyChannelsSettingsContent = {
[NOTIFY_CHANNELS.EMAIL]?: EmailNotifyChannelConfig;
[NOTIFY_CHANNELS.GOTIFY]?: GotifyNotifyChannelConfig;
[NOTIFY_CHANNELS.LARK]?: LarkNotifyChannelConfig;
[NOTIFY_CHANNELS.MATTERMOST]?: MattermostNotifyChannelConfig;
[NOTIFY_CHANNELS.PUSHOVER]?: PushoverNotifyChannelConfig;
[NOTIFY_CHANNELS.PUSHPLUS]?: PushPlusNotifyChannelConfig;
[NOTIFY_CHANNELS.SERVERCHAN]?: ServerChanNotifyChannelConfig;
@ -108,6 +110,14 @@ export type LarkNotifyChannelConfig = {
enabled?: boolean;
};
export type MattermostNotifyChannelConfig = {
serverUrl: string;
channel: string;
username: string;
password: string;
enabled?: boolean;
}
export type PushoverNotifyChannelConfig = {
token: string;
user: string;
@ -151,6 +161,7 @@ export const notifyChannelsMap: Map<NotifyChannel["type"], NotifyChannel> = new
[NOTIFY_CHANNELS.DINGTALK, "common.notifier.dingtalk"],
[NOTIFY_CHANNELS.GOTIFY, "common.notifier.gotify"],
[NOTIFY_CHANNELS.LARK, "common.notifier.lark"],
[NOTIFY_CHANNELS.MATTERMOST, "common.notifier.mattermost"],
[NOTIFY_CHANNELS.PUSHOVER, "common.notifier.pushover"],
[NOTIFY_CHANNELS.PUSHPLUS, "common.notifier.pushplus"],
[NOTIFY_CHANNELS.WECOM, "common.notifier.wecom"],

View File

@ -41,6 +41,7 @@
"common.notifier.email": "Email",
"common.notifier.gotify": "Gotify",
"common.notifier.lark": "Lark",
"common.notifier.mattermost": "Mattermost",
"common.notifier.pushover": "Pushover",
"common.notifier.pushplus": "PushPlus",
"common.notifier.serverchan": "ServerChan",

View File

@ -66,6 +66,16 @@
"settings.notification.channel.form.lark_webhook_url.label": "Webhook URL",
"settings.notification.channel.form.lark_webhook_url.placeholder": "Please enter Webhook URL",
"settings.notification.channel.form.lark_webhook_url.tooltip": "For more information, see <a href=\"https://www.feishu.cn/hc/en-US/articles/807992406756\" target=\"_blank\">https://www.feishu.cn/hc/en-US/articles/807992406756</a>",
"settings.notification.channel.form.mattermost_server_url.label": "Service URL",
"settings.notification.channel.form.mattermost_server_url.placeholder": "Please enter service URL",
"settings.notification.channel.form.mattermost_server_url.tooltip": "Example: <b>https://exmaple.com</b>, the protocol needs to be included but the trailing '/' should not be included.",
"settings.notification.channel.form.mattermost_channel_id.label": "Channel ID",
"settings.notification.channel.form.mattermost_channel_id.placeholder": "Please enter channel ID",
"settings.notification.channel.form.mattermost_channel_id.tooltip": "How to get the channel ID? Select the target channel from the left sidebar, click on the channel name at the top, and choose ”Channel Details.” You can directly see the channel ID on the pop-up page.",
"settings.notification.channel.form.mattermost_username.label": "Username",
"settings.notification.channel.form.mattermost_username.placeholder": "Please enter username",
"settings.notification.channel.form.mattermost_password.label": "Password",
"settings.notification.channel.form.mattermost_password.placeholder": "Please enter password",
"settings.notification.channel.form.pushover_token.placeholder": "Please enter Application API Token",
"settings.notification.channel.form.pushover_token.label": "Application API Token",
"settings.notification.channel.form.pushover_token.tooltip": "For more information, see <a href=\"https://pushover.net/api#registration\" target=\"_blank\">https://pushover.net/api#registration</a>",

View File

@ -41,6 +41,7 @@
"common.notifier.email": "邮件",
"common.notifier.gotify": "Gotify",
"common.notifier.lark": "飞书",
"common.notifier.mattermost": "Mattermost",
"common.notifier.pushover": "Pushover",
"common.notifier.pushplus": "PushPlus推送加",
"common.notifier.serverchan": "Server 酱",

View File

@ -66,6 +66,16 @@
"settings.notification.channel.form.lark_webhook_url.label": "机器人 Webhook 地址",
"settings.notification.channel.form.lark_webhook_url.placeholder": "请输入机器人 Webhook 地址",
"settings.notification.channel.form.lark_webhook_url.tooltip": "这是什么?请参阅 <a href=\"https://www.feishu.cn/hc/zh-CN/articles/807992406756\" target=\"_blank\">https://www.feishu.cn/hc/zh-CN/articles/807992406756</a>",
"settings.notification.channel.form.mattermost_server_url.label": "服务地址",
"settings.notification.channel.form.mattermost_server_url.placeholder": "请输入服务地址",
"settings.notification.channel.form.mattermost_server_url.tooltip": "示例: <b>https://exmaple.com</b>,需要包含协议但不要包含末尾的'/'",
"settings.notification.channel.form.mattermost_channel_id.label": "频道ID",
"settings.notification.channel.form.mattermost_channel_id.placeholder": "请输入频道ID",
"settings.notification.channel.form.mattermost_channel_id.tooltip": "频道ID怎么获取从左侧边栏中选择目标频道点击顶部的频道名称选择“频道详情”即可在弹出页面中直接看到频道ID",
"settings.notification.channel.form.mattermost_username.label": "用户名",
"settings.notification.channel.form.mattermost_username.placeholder": "请输入用户名",
"settings.notification.channel.form.mattermost_password.label": "密码",
"settings.notification.channel.form.mattermost_password.placeholder": "请输入密码",
"settings.notification.channel.form.pushover_token.placeholder": "请输入应用 API Token",
"settings.notification.channel.form.pushover_token.label": "应用 API Token",
"settings.notification.channel.form.pushover_token.tooltip": "这是什么?请参阅 <a href=\"https://pushover.net/api#registration\" target=\"_blank\">https://pushover.net/api#registration</a>",