diff --git a/internal/domain/notify.go b/internal/domain/notify.go
index 247c691f..4bc57b85 100644
--- a/internal/domain/notify.go
+++ b/internal/domain/notify.go
@@ -12,6 +12,7 @@ const (
NotifyChannelTypeBark = NotifyChannelType("bark")
NotifyChannelTypeDingTalk = NotifyChannelType("dingtalk")
NotifyChannelTypeEmail = NotifyChannelType("email")
+ NotifyChannelTypeGotify = NotifyChannelType("gotify")
NotifyChannelTypeLark = NotifyChannelType("lark")
NotifyChannelTypePushPlus = NotifyChannelType("pushplus")
NotifyChannelTypeServerChan = NotifyChannelType("serverchan")
diff --git a/internal/notify/providers.go b/internal/notify/providers.go
index a5f93a91..3a7cadf9 100644
--- a/internal/notify/providers.go
+++ b/internal/notify/providers.go
@@ -8,6 +8,7 @@ import (
pBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark"
pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk"
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"
pPushPlus "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushplus"
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"),
})
+ 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:
return pLark.NewNotifier(&pLark.NotifierConfig{
WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"),
diff --git a/internal/pkg/core/notifier/providers/gotify/gotify.go b/internal/pkg/core/notifier/providers/gotify/gotify.go
new file mode 100644
index 00000000..ad0c515e
--- /dev/null
+++ b/internal/pkg/core/notifier/providers/gotify/gotify.go
@@ -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 ¬ifier.NotifyResult{}, nil
+}
diff --git a/internal/pkg/core/notifier/providers/gotify/gotify_test.go b/internal/pkg/core/notifier/providers/gotify/gotify_test.go
new file mode 100644
index 00000000..31ad64af
--- /dev/null
+++ b/internal/pkg/core/notifier/providers/gotify/gotify_test.go
@@ -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)
+ })
+}
diff --git a/ui/src/components/notification/NotifyChannelEditForm.tsx b/ui/src/components/notification/NotifyChannelEditForm.tsx
index dbeddaaf..aa3f4f12 100644
--- a/ui/src/components/notification/NotifyChannelEditForm.tsx
+++ b/ui/src/components/notification/NotifyChannelEditForm.tsx
@@ -7,6 +7,7 @@ import { useAntdForm } from "@/hooks";
import NotifyChannelEditFormBarkFields from "./NotifyChannelEditFormBarkFields";
import NotifyChannelEditFormDingTalkFields from "./NotifyChannelEditFormDingTalkFields";
import NotifyChannelEditFormEmailFields from "./NotifyChannelEditFormEmailFields";
+import NotifyChannelEditFormGotifyFields from "./NotifyChannelEditFormGotifyFields.tsx";
import NotifyChannelEditFormLarkFields from "./NotifyChannelEditFormLarkFields";
import NotifyChannelEditFormPushPlusFields from "./NotifyChannelEditFormPushPlusFields";
import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServerChanFields";
@@ -49,6 +50,8 @@ const NotifyChannelEditForm = forwardRef;
case NOTIFY_CHANNELS.EMAIL:
return ;
+ case NOTIFY_CHANNELS.GOTIFY:
+ return ;
case NOTIFY_CHANNELS.LARK:
return ;
case NOTIFY_CHANNELS.PUSHPLUS:
diff --git a/ui/src/components/notification/NotifyChannelEditFormGotifyFields.tsx b/ui/src/components/notification/NotifyChannelEditFormGotifyFields.tsx
new file mode 100644
index 00000000..189f4e16
--- /dev/null
+++ b/ui/src/components/notification/NotifyChannelEditFormGotifyFields.tsx
@@ -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 (
+ <>
+
}
+ >
+
+
+ }
+ >
+
+
+ }
+ >
+
+
+ >
+ );
+};
+
+export default NotifyChannelEditFormGotifyFields;
diff --git a/ui/src/domain/settings.ts b/ui/src/domain/settings.ts
index 16a0ae69..0ab156ed 100644
--- a/ui/src/domain/settings.ts
+++ b/ui/src/domain/settings.ts
@@ -40,6 +40,7 @@ export const NOTIFY_CHANNELS = Object.freeze({
BARK: "bark",
DINGTALK: "dingtalk",
EMAIL: "email",
+ GOTIFY: "gotify",
LARK: "lark",
PUSHPLUS: "pushplus",
SERVERCHAN: "serverchan",
@@ -59,6 +60,7 @@ export type NotifyChannelsSettingsContent = {
[NOTIFY_CHANNELS.BARK]?: BarkNotifyChannelConfig;
[NOTIFY_CHANNELS.DINGTALK]?: DingTalkNotifyChannelConfig;
[NOTIFY_CHANNELS.EMAIL]?: EmailNotifyChannelConfig;
+ [NOTIFY_CHANNELS.GOTIFY]?: GotifyNotifyChannelConfig;
[NOTIFY_CHANNELS.LARK]?: LarkNotifyChannelConfig;
[NOTIFY_CHANNELS.PUSHPLUS]?: PushPlusNotifyChannelConfig;
[NOTIFY_CHANNELS.SERVERCHAN]?: ServerChanNotifyChannelConfig;
@@ -90,6 +92,13 @@ export type DingTalkNotifyChannelConfig = {
enabled?: boolean;
};
+export type GotifyNotifyChannelConfig = {
+ url: string;
+ token: string;
+ priority: string;
+ enabled?: boolean;
+};
+
export type LarkNotifyChannelConfig = {
webhookUrl: string;
enabled?: boolean;
@@ -130,6 +139,7 @@ export const notifyChannelsMap: Map = new
[
[NOTIFY_CHANNELS.EMAIL, "common.notifier.email"],
[NOTIFY_CHANNELS.DINGTALK, "common.notifier.dingtalk"],
+ [NOTIFY_CHANNELS.GOTIFY, "common.notifier.gotify"],
[NOTIFY_CHANNELS.LARK, "common.notifier.lark"],
[NOTIFY_CHANNELS.PUSHPLUS, "common.notifier.pushplus"],
[NOTIFY_CHANNELS.WECOM, "common.notifier.wecom"],
diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json
index 42911b88..df6495fa 100644
--- a/ui/src/i18n/locales/en/nls.common.json
+++ b/ui/src/i18n/locales/en/nls.common.json
@@ -38,6 +38,7 @@
"common.notifier.bark": "Bark",
"common.notifier.dingtalk": "DingTalk",
"common.notifier.email": "Email",
+ "common.notifier.gotify": "Gotify",
"common.notifier.lark": "Lark",
"common.notifier.pushplus": "PushPlus",
"common.notifier.serverchan": "ServerChan",
diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json
index 642ad59a..596b89c4 100644
--- a/ui/src/i18n/locales/en/nls.settings.json
+++ b/ui/src/i18n/locales/en/nls.settings.json
@@ -53,6 +53,16 @@
"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.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: https://gotify.exmaple.com, the protocol needs to be included but the trailing '/' should not be included.
For more information, see https://gotify.net/docs/pushmsg",
+ "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 https://gotify.net/docs/pushmsg",
+ "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.
For more information, see https://gotify.net/docs/pushmsg
https://github.com/gotify/android/issues/18#issuecomment-437403888",
+ "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.placeholder": "Please enter Webhook URL",
"settings.notification.channel.form.lark_webhook_url.tooltip": "For more information, see https://www.feishu.cn/hc/en-US/articles/807992406756",
diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json
index 76b5dfd6..466e83ac 100644
--- a/ui/src/i18n/locales/zh/nls.common.json
+++ b/ui/src/i18n/locales/zh/nls.common.json
@@ -38,6 +38,7 @@
"common.notifier.bark": "Bark",
"common.notifier.dingtalk": "钉钉",
"common.notifier.email": "邮件",
+ "common.notifier.gotify": "Gotify",
"common.notifier.lark": "飞书",
"common.notifier.pushplus": "PushPlus推送加",
"common.notifier.serverchan": "Server 酱",
diff --git a/ui/src/i18n/locales/zh/nls.settings.json b/ui/src/i18n/locales/zh/nls.settings.json
index 0d593c80..ad9bcc95 100644
--- a/ui/src/i18n/locales/zh/nls.settings.json
+++ b/ui/src/i18n/locales/zh/nls.settings.json
@@ -53,6 +53,16 @@
"settings.notification.channel.form.email_sender_address.placeholder": "请输入发送邮箱地址",
"settings.notification.channel.form.email_receiver_address.label": "接收邮箱地址",
"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": "示例: https://gotify.exmaple.com>,需要包含协议但不要包含末尾的'/'。
请参阅 https://gotify.net/docs/pushmsg",
+ "settings.notification.channel.form.gotify_token.placeholder": "请输入应用Token",
+ "settings.notification.channel.form.gotify_token.label": "应用Token",
+ "settings.notification.channel.form.gotify_token.tooltip": "应用Token。
请参阅 https://gotify.net/docs/pushmsg",
+ "settings.notification.channel.form.gotify_priority.placeholder": "请输入消息优先级",
+ "settings.notification.channel.form.gotify_priority.label": "消息优先级",
+ "settings.notification.channel.form.gotify_priority.tooltip": "消息优先级, 可以设置为1作为默认值。
请参阅 https://gotify.net/docs/pushmsg
https://github.com/gotify/android/issues/18#issuecomment-437403888",
+ "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.placeholder": "请输入机器人 Webhook 地址",
"settings.notification.channel.form.lark_webhook_url.tooltip": "这是什么?请参阅 https://www.feishu.cn/hc/zh-CN/articles/807992406756",