Merge pull request #579 from catfishlty/feat/gotify

feat(notify): add gotify
This commit is contained in:
Yoan.liu 2025-04-03 17:42:00 +08:00 committed by GitHub
commit 9f8aa15af8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 262 additions and 0 deletions

View File

@ -12,6 +12,7 @@ const (
NotifyChannelTypeBark = NotifyChannelType("bark") NotifyChannelTypeBark = NotifyChannelType("bark")
NotifyChannelTypeDingTalk = NotifyChannelType("dingtalk") NotifyChannelTypeDingTalk = NotifyChannelType("dingtalk")
NotifyChannelTypeEmail = NotifyChannelType("email") NotifyChannelTypeEmail = NotifyChannelType("email")
NotifyChannelTypeGotify = NotifyChannelType("gotify")
NotifyChannelTypeLark = NotifyChannelType("lark") NotifyChannelTypeLark = NotifyChannelType("lark")
NotifyChannelTypePushPlus = NotifyChannelType("pushplus") NotifyChannelTypePushPlus = NotifyChannelType("pushplus")
NotifyChannelTypeServerChan = NotifyChannelType("serverchan") NotifyChannelTypeServerChan = NotifyChannelType("serverchan")

View File

@ -8,6 +8,7 @@ import (
pBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark" pBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark"
pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk" pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk"
pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" 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" pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark"
pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus" pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus"
pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan" pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan"
@ -46,6 +47,13 @@ func createNotifier(channel domain.NotifyChannelType, channelConfig map[string]a
ReceiverAddress: maputil.GetString(channelConfig, "receiverAddress"), ReceiverAddress: maputil.GetString(channelConfig, "receiverAddress"),
}) })
case domain.NotifyChannelTypeGotify:
return pGotify.NewNotifier(&pGotify.NotifierConfig{
Url: maputil.GetString(channelConfig, "url"),
Token: maputil.GetString(channelConfig, "token"),
Priority: maputil.GetOrDefaultInt64(channelConfig, "priority", 1),
})
case domain.NotifyChannelTypeLark: case domain.NotifyChannelTypeLark:
return pLark.NewNotifier(&pLark.NotifierConfig{ return pLark.NewNotifier(&pLark.NotifierConfig{
WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"), WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"),

View File

@ -0,0 +1,104 @@
package gotify
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// Gotify 服务地址
// 示例https://gotify.example.com
Url string `json:"url"`
// Gotify Token
Token string `json:"token"`
// Gotify 消息优先级
Priority int64 `json:"priority"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
// 未来将移除
httpClient *http.Client
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
return &NotifierProvider{
config: config,
httpClient: http.DefaultClient,
}, 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) {
// Gotify 原生实现, notify 库没有实现, 等待合并
reqBody := &struct {
Title string `json:"title"`
Message string `json:"message"`
Priority int64 `json:"priority"`
}{
Title: subject,
Message: message,
Priority: n.config.Priority,
}
// Make request
body, err := json.Marshal(reqBody)
if err != nil {
return nil, errors.Wrap(err, "encode message body")
}
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
fmt.Sprintf("%s/message", n.config.Url),
bytes.NewReader(body),
)
if err != nil {
return nil, errors.Wrap(err, "create new request")
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", n.config.Token))
req.Header.Set("Content-Type", "application/json; charset=utf-8")
// Send request to gotify service
resp, err := n.httpClient.Do(req)
if err != nil {
return nil, errors.Wrapf(err, "send request to gotify server")
}
defer resp.Body.Close()
// Read response and verify success
result, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "read response")
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("gotify returned status code %d: %s", resp.StatusCode, string(result))
}
return &notifier.NotifyResult{}, nil
}

View File

@ -0,0 +1,68 @@
package gotify_test
import (
"context"
"flag"
"fmt"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/gotify"
)
const (
mockSubject = "test_subject"
mockMessage = "test_message"
)
var (
fUrl string
fToken string
fPriority int64
)
func init() {
argsPrefix := "CERTIMATE_NOTIFIER_GOTIFY_"
flag.StringVar(&fUrl, argsPrefix+"URL", "", "")
flag.StringVar(&fToken, argsPrefix+"TOKEN", "", "")
flag.Int64Var(&fPriority, argsPrefix+"PRIORITY", 0, "")
}
/*
Shell command to run this test:
go test -v ./gotify_test.go -args \
--CERTIMATE_NOTIFIER_GOTIFY_URL="https://example.com" \
--CERTIMATE_NOTIFIER_GOTIFY_TOKEN="your-gotify-application-token" \
--CERTIMATE_NOTIFIER_GOTIFY_PRIORITY="your-message-priority"
*/
func TestNotify(t *testing.T) {
flag.Parse()
t.Run("Notify", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("URL: %v", fUrl),
fmt.Sprintf("TOKEN: %v", fToken),
fmt.Sprintf("PRIORITY: %d", fPriority),
}, "\n"))
notifier, err := provider.NewNotifier(&provider.NotifierConfig{
Url: fUrl,
Token: fToken,
Priority: fPriority,
})
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

@ -7,6 +7,7 @@ import { useAntdForm } from "@/hooks";
import NotifyChannelEditFormBarkFields from "./NotifyChannelEditFormBarkFields"; import NotifyChannelEditFormBarkFields from "./NotifyChannelEditFormBarkFields";
import NotifyChannelEditFormDingTalkFields from "./NotifyChannelEditFormDingTalkFields"; import NotifyChannelEditFormDingTalkFields from "./NotifyChannelEditFormDingTalkFields";
import NotifyChannelEditFormEmailFields from "./NotifyChannelEditFormEmailFields"; import NotifyChannelEditFormEmailFields from "./NotifyChannelEditFormEmailFields";
import NotifyChannelEditFormGotifyFields from "./NotifyChannelEditFormGotifyFields.tsx";
import NotifyChannelEditFormLarkFields from "./NotifyChannelEditFormLarkFields"; import NotifyChannelEditFormLarkFields from "./NotifyChannelEditFormLarkFields";
import NotifyChannelEditFormPushPlusFields from "./NotifyChannelEditFormPushPlusFields"; import NotifyChannelEditFormPushPlusFields from "./NotifyChannelEditFormPushPlusFields";
import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServerChanFields"; import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServerChanFields";
@ -49,6 +50,8 @@ const NotifyChannelEditForm = forwardRef<NotifyChannelEditFormInstance, NotifyCh
return <NotifyChannelEditFormDingTalkFields />; return <NotifyChannelEditFormDingTalkFields />;
case NOTIFY_CHANNELS.EMAIL: case NOTIFY_CHANNELS.EMAIL:
return <NotifyChannelEditFormEmailFields />; return <NotifyChannelEditFormEmailFields />;
case NOTIFY_CHANNELS.GOTIFY:
return <NotifyChannelEditFormGotifyFields />;
case NOTIFY_CHANNELS.LARK: case NOTIFY_CHANNELS.LARK:
return <NotifyChannelEditFormLarkFields />; return <NotifyChannelEditFormLarkFields />;
case NOTIFY_CHANNELS.PUSHPLUS: case NOTIFY_CHANNELS.PUSHPLUS:

View File

@ -0,0 +1,46 @@
import { useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
const NotifyChannelEditFormGotifyFields = () => {
const { t } = useTranslation();
const formSchema = z.object({
url: z.string({ message: t("settings.notification.channel.form.gotify_url.placeholder") }).url({ message: t("common.errmsg.url_invalid") }),
token: z.string({ message: t("settings.notification.channel.form.gotify_token.placeholder") }),
priority: z.preprocess(val => Number(val), z.number({ message: t("settings.notification.channel.form.gotify_priority.placeholder") }).gte(0, { message: t("settings.notification.channel.form.gotify_priority.error.gte0") })),
});
const formRule = createSchemaFieldRule(formSchema);
return (
<>
<Form.Item
name="url"
label={t("settings.notification.channel.form.gotify_url.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.notification.channel.form.gotify_url.tooltip") }}></span>}
>
<Input placeholder={t("settings.notification.channel.form.gotify_url.placeholder")} />
</Form.Item>
<Form.Item
name="token"
label={t("settings.notification.channel.form.gotify_token.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.notification.channel.form.gotify_token.tooltip") }}></span>}
>
<Input placeholder={t("settings.notification.channel.form.gotify_token.placeholder")} />
</Form.Item>
<Form.Item
name="priority"
label={t("settings.notification.channel.form.gotify_priority.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("settings.notification.channel.form.gotify_priority.tooltip") }}></span>}
>
<Input placeholder={t("settings.notification.channel.form.gotify_priority.placeholder")} />
</Form.Item>
</>
);
};
export default NotifyChannelEditFormGotifyFields;

View File

@ -40,6 +40,7 @@ export const NOTIFY_CHANNELS = Object.freeze({
BARK: "bark", BARK: "bark",
DINGTALK: "dingtalk", DINGTALK: "dingtalk",
EMAIL: "email", EMAIL: "email",
GOTIFY: "gotify",
LARK: "lark", LARK: "lark",
PUSHPLUS: "pushplus", PUSHPLUS: "pushplus",
SERVERCHAN: "serverchan", SERVERCHAN: "serverchan",
@ -59,6 +60,7 @@ export type NotifyChannelsSettingsContent = {
[NOTIFY_CHANNELS.BARK]?: BarkNotifyChannelConfig; [NOTIFY_CHANNELS.BARK]?: BarkNotifyChannelConfig;
[NOTIFY_CHANNELS.DINGTALK]?: DingTalkNotifyChannelConfig; [NOTIFY_CHANNELS.DINGTALK]?: DingTalkNotifyChannelConfig;
[NOTIFY_CHANNELS.EMAIL]?: EmailNotifyChannelConfig; [NOTIFY_CHANNELS.EMAIL]?: EmailNotifyChannelConfig;
[NOTIFY_CHANNELS.GOTIFY]?: GotifyNotifyChannelConfig;
[NOTIFY_CHANNELS.LARK]?: LarkNotifyChannelConfig; [NOTIFY_CHANNELS.LARK]?: LarkNotifyChannelConfig;
[NOTIFY_CHANNELS.PUSHPLUS]?: PushPlusNotifyChannelConfig; [NOTIFY_CHANNELS.PUSHPLUS]?: PushPlusNotifyChannelConfig;
[NOTIFY_CHANNELS.SERVERCHAN]?: ServerChanNotifyChannelConfig; [NOTIFY_CHANNELS.SERVERCHAN]?: ServerChanNotifyChannelConfig;
@ -90,6 +92,13 @@ export type DingTalkNotifyChannelConfig = {
enabled?: boolean; enabled?: boolean;
}; };
export type GotifyNotifyChannelConfig = {
url: string;
token: string;
priority: string;
enabled?: boolean;
};
export type LarkNotifyChannelConfig = { export type LarkNotifyChannelConfig = {
webhookUrl: string; webhookUrl: string;
enabled?: boolean; enabled?: boolean;
@ -130,6 +139,7 @@ export const notifyChannelsMap: Map<NotifyChannel["type"], NotifyChannel> = new
[ [
[NOTIFY_CHANNELS.EMAIL, "common.notifier.email"], [NOTIFY_CHANNELS.EMAIL, "common.notifier.email"],
[NOTIFY_CHANNELS.DINGTALK, "common.notifier.dingtalk"], [NOTIFY_CHANNELS.DINGTALK, "common.notifier.dingtalk"],
[NOTIFY_CHANNELS.GOTIFY, "common.notifier.gotify"],
[NOTIFY_CHANNELS.LARK, "common.notifier.lark"], [NOTIFY_CHANNELS.LARK, "common.notifier.lark"],
[NOTIFY_CHANNELS.PUSHPLUS, "common.notifier.pushplus"], [NOTIFY_CHANNELS.PUSHPLUS, "common.notifier.pushplus"],
[NOTIFY_CHANNELS.WECOM, "common.notifier.wecom"], [NOTIFY_CHANNELS.WECOM, "common.notifier.wecom"],

View File

@ -38,6 +38,7 @@
"common.notifier.bark": "Bark", "common.notifier.bark": "Bark",
"common.notifier.dingtalk": "DingTalk", "common.notifier.dingtalk": "DingTalk",
"common.notifier.email": "Email", "common.notifier.email": "Email",
"common.notifier.gotify": "Gotify",
"common.notifier.lark": "Lark", "common.notifier.lark": "Lark",
"common.notifier.pushplus": "PushPlus", "common.notifier.pushplus": "PushPlus",
"common.notifier.serverchan": "ServerChan", "common.notifier.serverchan": "ServerChan",

View File

@ -53,6 +53,16 @@
"settings.notification.channel.form.email_sender_address.placeholder": "Please enter sender email address", "settings.notification.channel.form.email_sender_address.placeholder": "Please enter sender email address",
"settings.notification.channel.form.email_receiver_address.label": "Receiver email address", "settings.notification.channel.form.email_receiver_address.label": "Receiver email address",
"settings.notification.channel.form.email_receiver_address.placeholder": "Please enter receiver email address", "settings.notification.channel.form.email_receiver_address.placeholder": "Please enter receiver email address",
"settings.notification.channel.form.gotify_url.placeholder": "Please enter Service URL",
"settings.notification.channel.form.gotify_url.label": "Service URL",
"settings.notification.channel.form.gotify_url.tooltip": "Example: <b>https://gotify.exmaple.com</b>, the protocol needs to be included but the trailing '/' should not be included.<br>For more information, see <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a>",
"settings.notification.channel.form.gotify_token.placeholder": "Please enter Application Token",
"settings.notification.channel.form.gotify_token.label": "Application Token",
"settings.notification.channel.form.gotify_token.tooltip": "For more information, see <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a>",
"settings.notification.channel.form.gotify_priority.placeholder": "Please enter message priority",
"settings.notification.channel.form.gotify_priority.label": "Message Priority",
"settings.notification.channel.form.gotify_priority.tooltip": "Message Priority, you can set it to 1 as default.<br>For more information, see <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a><br><a href=\"https://github.com/gotify/android/issues/18#issuecomment-437403888\" target=\"_blank\">https://github.com/gotify/android/issues/18#issuecomment-437403888</a>",
"settings.notification.channel.form.gotify_priority.error.gte0": "Message Priority must be greater than or equal to 0.",
"settings.notification.channel.form.lark_webhook_url.label": "Webhook URL", "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.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.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>",

View File

@ -38,6 +38,7 @@
"common.notifier.bark": "Bark", "common.notifier.bark": "Bark",
"common.notifier.dingtalk": "钉钉", "common.notifier.dingtalk": "钉钉",
"common.notifier.email": "邮件", "common.notifier.email": "邮件",
"common.notifier.gotify": "Gotify",
"common.notifier.lark": "飞书", "common.notifier.lark": "飞书",
"common.notifier.pushplus": "PushPlus推送加", "common.notifier.pushplus": "PushPlus推送加",
"common.notifier.serverchan": "Server 酱", "common.notifier.serverchan": "Server 酱",

View File

@ -53,6 +53,16 @@
"settings.notification.channel.form.email_sender_address.placeholder": "请输入发送邮箱地址", "settings.notification.channel.form.email_sender_address.placeholder": "请输入发送邮箱地址",
"settings.notification.channel.form.email_receiver_address.label": "接收邮箱地址", "settings.notification.channel.form.email_receiver_address.label": "接收邮箱地址",
"settings.notification.channel.form.email_receiver_address.placeholder": "请输入接收邮箱地址", "settings.notification.channel.form.email_receiver_address.placeholder": "请输入接收邮箱地址",
"settings.notification.channel.form.gotify_url.placeholder": "请输入服务地址",
"settings.notification.channel.form.gotify_url.label": "服务地址",
"settings.notification.channel.form.gotify_url.tooltip": "示例: <b>https://gotify.exmaple.com</>,需要包含协议但不要包含末尾的'/'。<br>请参阅 <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a>",
"settings.notification.channel.form.gotify_token.placeholder": "请输入应用Token",
"settings.notification.channel.form.gotify_token.label": "应用Token",
"settings.notification.channel.form.gotify_token.tooltip": "应用Token。<br>请参阅 <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a>",
"settings.notification.channel.form.gotify_priority.placeholder": "请输入消息优先级",
"settings.notification.channel.form.gotify_priority.label": "消息优先级",
"settings.notification.channel.form.gotify_priority.tooltip": "消息优先级, 可以设置为1作为默认值。<br>请参阅 <a href=\"https://gotify.net/docs/pushmsg\" target=\"_blank\">https://gotify.net/docs/pushmsg</a><br><a href=\"https://github.com/gotify/android/issues/18#issuecomment-437403888\" target=\"_blank\">https://github.com/gotify/android/issues/18#issuecomment-437403888</a>",
"settings.notification.channel.form.gotify_priority.error.gte0": "消息优先级需要大于等于0",
"settings.notification.channel.form.lark_webhook_url.label": "机器人 Webhook 地址", "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.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.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>",