From 6b8dbf52352cd5a6c297819aaedc37fd219e13ae Mon Sep 17 00:00:00 2001 From: imlonghao Date: Sat, 12 Apr 2025 11:07:32 +0800 Subject: [PATCH] feat: support pushover as notification --- internal/domain/notify.go | 1 + internal/notify/providers.go | 7 ++ .../notifier/providers/pushover/pushover.go | 102 ++++++++++++++++++ .../providers/pushover/pushover_test.go | 62 +++++++++++ .../notification/NotifyChannelEditForm.tsx | 3 + .../NotifyChannelEditFormPushoverFields.tsx | 41 +++++++ ui/src/domain/settings.ts | 9 ++ ui/src/i18n/locales/en/nls.common.json | 1 + ui/src/i18n/locales/en/nls.settings.json | 6 ++ ui/src/i18n/locales/zh/nls.common.json | 1 + ui/src/i18n/locales/zh/nls.settings.json | 6 ++ 11 files changed, 239 insertions(+) create mode 100644 internal/pkg/core/notifier/providers/pushover/pushover.go create mode 100644 internal/pkg/core/notifier/providers/pushover/pushover_test.go create mode 100644 ui/src/components/notification/NotifyChannelEditFormPushoverFields.tsx diff --git a/internal/domain/notify.go b/internal/domain/notify.go index 4bc57b85..7be8b59b 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") + NotifyChannelTypePushover = NotifyChannelType("pushover") NotifyChannelTypePushPlus = NotifyChannelType("pushplus") NotifyChannelTypeServerChan = NotifyChannelType("serverchan") NotifyChannelTypeTelegram = NotifyChannelType("telegram") diff --git a/internal/notify/providers.go b/internal/notify/providers.go index 3a7cadf9..808892b3 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" + 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" pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram" @@ -59,6 +60,12 @@ func createNotifier(channel domain.NotifyChannelType, channelConfig map[string]a WebhookUrl: maputil.GetString(channelConfig, "webhookUrl"), }) + case domain.NotifyChannelTypePushover: + return pPushover.NewNotifier(&pPushover.NotifierConfig{ + Token: maputil.GetString(channelConfig, "token"), + User: maputil.GetString(channelConfig, "user"), + }) + case domain.NotifyChannelTypePushPlus: return pPushPlus.NewNotifier(&pPushPlus.NotifierConfig{ Token: maputil.GetString(channelConfig, "token"), diff --git a/internal/pkg/core/notifier/providers/pushover/pushover.go b/internal/pkg/core/notifier/providers/pushover/pushover.go new file mode 100644 index 00000000..8f84dfd2 --- /dev/null +++ b/internal/pkg/core/notifier/providers/pushover/pushover.go @@ -0,0 +1,102 @@ +package pushover + +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 { + Token string `json:"token"` // 应用 API Token + User string `json:"user"` // 用户/分组 Key +} + +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 +} + +// Notify 发送通知 +// 参考文档:https://pushover.net/api +func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { + // 请求体 + reqBody := &struct { + Token string `json:"token"` + User string `json:"user"` + Title string `json:"title"` + Message string `json:"message"` + }{ + Token: n.config.Token, + User: n.config.User, + Title: subject, + Message: message, + } + + // 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, + "https://api.pushover.net/1/messages.json", + bytes.NewReader(body), + ) + if err != nil { + return nil, errors.Wrap(err, "create new request") + } + + req.Header.Set("Content-Type", "application/json; charset=utf-8") + + // Send request to pushover service + resp, err := n.httpClient.Do(req) + if err != nil { + return nil, errors.Wrapf(err, "send request to pushover server") + } + defer resp.Body.Close() + + 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("pushover returned status code %d: %s", resp.StatusCode, string(result)) + } + + return ¬ifier.NotifyResult{}, nil +} diff --git a/internal/pkg/core/notifier/providers/pushover/pushover_test.go b/internal/pkg/core/notifier/providers/pushover/pushover_test.go new file mode 100644 index 00000000..450beac1 --- /dev/null +++ b/internal/pkg/core/notifier/providers/pushover/pushover_test.go @@ -0,0 +1,62 @@ +package pushover_test + +import ( + "context" + "flag" + "fmt" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/pushover" +) + +const ( + mockSubject = "test_subject" + mockMessage = "test_message" +) + +var ( + fToken string + fUser string +) + +func init() { + argsPrefix := "CERTIMATE_NOTIFIER_PUSHOVER_" + flag.StringVar(&fToken, argsPrefix+"TOKEN", "", "") + flag.StringVar(&fUser, argsPrefix+"USER", "", "") +} + +/* +Shell command to run this test: + + go test -v ./pushover_test.go -args \ + --CERTIMATE_NOTIFIER_PUSHOVER_TOKEN="your-pushover-token" \ + --CERTIMATE_NOTIFIER_PUSHOVER_USER="your-pushover-user" \ +*/ +func TestNotify(t *testing.T) { + flag.Parse() + + t.Run("Notify", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("TOKEN: %v", fToken), + }, "\n")) + + notifier, err := provider.NewNotifier(&provider.NotifierConfig{ + Token: fToken, + User: fUser, + }) + 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..1aa7eb87 100644 --- a/ui/src/components/notification/NotifyChannelEditForm.tsx +++ b/ui/src/components/notification/NotifyChannelEditForm.tsx @@ -9,6 +9,7 @@ import NotifyChannelEditFormDingTalkFields from "./NotifyChannelEditFormDingTalk import NotifyChannelEditFormEmailFields from "./NotifyChannelEditFormEmailFields"; import NotifyChannelEditFormGotifyFields from "./NotifyChannelEditFormGotifyFields.tsx"; import NotifyChannelEditFormLarkFields from "./NotifyChannelEditFormLarkFields"; +import NotifyChannelEditFormPushoverFields from "./NotifyChannelEditFormPushoverFields"; import NotifyChannelEditFormPushPlusFields from "./NotifyChannelEditFormPushPlusFields"; import NotifyChannelEditFormServerChanFields from "./NotifyChannelEditFormServerChanFields"; import NotifyChannelEditFormTelegramFields from "./NotifyChannelEditFormTelegramFields"; @@ -54,6 +55,8 @@ const NotifyChannelEditForm = forwardRef; case NOTIFY_CHANNELS.LARK: return ; + case NOTIFY_CHANNELS.PUSHOVER: + return ; case NOTIFY_CHANNELS.PUSHPLUS: return ; case NOTIFY_CHANNELS.SERVERCHAN: diff --git a/ui/src/components/notification/NotifyChannelEditFormPushoverFields.tsx b/ui/src/components/notification/NotifyChannelEditFormPushoverFields.tsx new file mode 100644 index 00000000..449c98fa --- /dev/null +++ b/ui/src/components/notification/NotifyChannelEditFormPushoverFields.tsx @@ -0,0 +1,41 @@ +import { useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +const NotifyChannelEditFormPushoverFields = () => { + const { t } = useTranslation(); + + const formSchema = z.object({ + token: z + .string({ message: t("settings.notification.channel.form.pushover_token.placeholder") }) + .nonempty(t("settings.notification.channel.form.pushover_token.placeholder")), + user: z + .string({ message: t("settings.notification.channel.form.pushover_user.placeholder") }) + .nonempty(t("settings.notification.channel.form.pushover_user.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + return ( + <> + } + > + + + } + > + + + + ); +}; + +export default NotifyChannelEditFormPushoverFields; diff --git a/ui/src/domain/settings.ts b/ui/src/domain/settings.ts index 5dc4da80..11633789 100644 --- a/ui/src/domain/settings.ts +++ b/ui/src/domain/settings.ts @@ -44,6 +44,7 @@ export const NOTIFY_CHANNELS = Object.freeze({ EMAIL: "email", GOTIFY: "gotify", LARK: "lark", + PUSHOVER: "pushover", PUSHPLUS: "pushplus", SERVERCHAN: "serverchan", TELEGRAM: "telegram", @@ -64,6 +65,7 @@ export type NotifyChannelsSettingsContent = { [NOTIFY_CHANNELS.EMAIL]?: EmailNotifyChannelConfig; [NOTIFY_CHANNELS.GOTIFY]?: GotifyNotifyChannelConfig; [NOTIFY_CHANNELS.LARK]?: LarkNotifyChannelConfig; + [NOTIFY_CHANNELS.PUSHOVER]?: PushoverNotifyChannelConfig; [NOTIFY_CHANNELS.PUSHPLUS]?: PushPlusNotifyChannelConfig; [NOTIFY_CHANNELS.SERVERCHAN]?: ServerChanNotifyChannelConfig; [NOTIFY_CHANNELS.TELEGRAM]?: TelegramNotifyChannelConfig; @@ -106,6 +108,12 @@ export type LarkNotifyChannelConfig = { enabled?: boolean; }; +export type PushoverNotifyChannelConfig = { + token: string; + user: string; + enabled?: boolean; +}; + export type PushPlusNotifyChannelConfig = { token: string; enabled?: boolean; @@ -143,6 +151,7 @@ export const notifyChannelsMap: Map = new [NOTIFY_CHANNELS.DINGTALK, "common.notifier.dingtalk"], [NOTIFY_CHANNELS.GOTIFY, "common.notifier.gotify"], [NOTIFY_CHANNELS.LARK, "common.notifier.lark"], + [NOTIFY_CHANNELS.PUSHOVER, "common.notifier.pushover"], [NOTIFY_CHANNELS.PUSHPLUS, "common.notifier.pushplus"], [NOTIFY_CHANNELS.WECOM, "common.notifier.wecom"], [NOTIFY_CHANNELS.TELEGRAM, "common.notifier.telegram"], diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index c5949d28..5ca42847 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -41,6 +41,7 @@ "common.notifier.email": "Email", "common.notifier.gotify": "Gotify", "common.notifier.lark": "Lark", + "common.notifier.pushover": "Pushover", "common.notifier.pushplus": "PushPlus", "common.notifier.serverchan": "ServerChan", "common.notifier.telegram": "Telegram", diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json index d436665c..eb3aeb95 100644 --- a/ui/src/i18n/locales/en/nls.settings.json +++ b/ui/src/i18n/locales/en/nls.settings.json @@ -66,6 +66,12 @@ "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", + "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 https://pushover.net/api#registration", + "settings.notification.channel.form.pushover_user.placeholder": "Please enter User/Group Key", + "settings.notification.channel.form.pushover_user.label": "User/Group Key", + "settings.notification.channel.form.pushover_user.tooltip": "For more information, see https://pushover.net/api#identifiers", "settings.notification.channel.form.pushplus_token.placeholder": "Please enter Token", "settings.notification.channel.form.pushplus_token.label": "Token", "settings.notification.channel.form.pushplus_token.tooltip": "For more information, see https://www.pushplus.plus/push1.html", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index 726c5ca2..19f0a94a 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -41,6 +41,7 @@ "common.notifier.email": "邮件", "common.notifier.gotify": "Gotify", "common.notifier.lark": "飞书", + "common.notifier.pushover": "Pushover", "common.notifier.pushplus": "PushPlus推送加", "common.notifier.serverchan": "Server 酱", "common.notifier.telegram": "Telegram", diff --git a/ui/src/i18n/locales/zh/nls.settings.json b/ui/src/i18n/locales/zh/nls.settings.json index c00d158a..b892d50c 100644 --- a/ui/src/i18n/locales/zh/nls.settings.json +++ b/ui/src/i18n/locales/zh/nls.settings.json @@ -66,6 +66,12 @@ "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", + "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": "这是什么?请参阅 https://pushover.net/api#registration", + "settings.notification.channel.form.pushover_user.placeholder": "请输入用户/分组 Key", + "settings.notification.channel.form.pushover_user.label": "用户/分组 Key", + "settings.notification.channel.form.pushover_user.tooltip": "这是什么?请参阅 https://pushover.net/api#identifiers", "settings.notification.channel.form.pushplus_token.placeholder": "请输入Token", "settings.notification.channel.form.pushplus_token.label": "Token", "settings.notification.channel.form.pushplus_token.tooltip": "请参阅 https://www.pushplus.plus/push1.html",