mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-31 00:52:35 +02:00
implement ntfy notification
This commit is contained in:
parent
6ae391a3c9
commit
c00395196f
12 changed files with 143 additions and 15 deletions
|
@ -1,6 +1,7 @@
|
|||
package notif
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
@ -54,3 +55,15 @@ func (base *ProviderBase) GetMethod() string {
|
|||
func (base *ProviderBase) GetMIMEType() string {
|
||||
return "application/json"
|
||||
}
|
||||
|
||||
func (base *ProviderBase) SetHeaders(logMsg *LogMessage, headers http.Header) {
|
||||
// no-op by default
|
||||
}
|
||||
|
||||
func (base *ProviderBase) makeRespError(resp *http.Response) error {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
return E.Errorf("%s status %d: %s", base.Name, resp.StatusCode, body)
|
||||
}
|
||||
return E.Errorf("%s status %d", base.Name, resp.StatusCode)
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ import "fmt"
|
|||
type Color uint
|
||||
|
||||
const (
|
||||
Red Color = 0xff0000
|
||||
Green Color = 0x00ff00
|
||||
Blue Color = 0x0000ff
|
||||
ColorError Color = 0xff0000
|
||||
ColorSuccess Color = 0x00ff00
|
||||
ColorInfo Color = 0x0000ff
|
||||
)
|
||||
|
||||
func (c Color) HexString() string {
|
||||
|
|
|
@ -38,6 +38,8 @@ func (cfg *NotificationConfig) UnmarshalMap(m map[string]any) (err E.Error) {
|
|||
cfg.Provider = &Webhook{}
|
||||
case ProviderGotify:
|
||||
cfg.Provider = &GotifyClient{}
|
||||
case ProviderNtfy:
|
||||
cfg.Provider = &Ntfy{}
|
||||
default:
|
||||
return ErrUnknownNotifProvider.
|
||||
Subject(cfg.ProviderName).
|
||||
|
|
|
@ -61,7 +61,7 @@ func (client *GotifyClient) makeRespError(resp *http.Response) error {
|
|||
var errm model.Error
|
||||
err := json.NewDecoder(resp.Body).Decode(&errm)
|
||||
if err != nil {
|
||||
return fmt.Errorf(ProviderGotify+" status %d, but failed to decode err response: %w", resp.StatusCode, err)
|
||||
return fmt.Errorf("%s status %d, but failed to decode err response: %w", client.Name, resp.StatusCode, err)
|
||||
}
|
||||
return fmt.Errorf(ProviderGotify+" status %d %s: %s", resp.StatusCode, errm.Error, errm.ErrorDescription)
|
||||
return fmt.Errorf("%s status %d %s: %s", client.Name, resp.StatusCode, errm.Error, errm.ErrorDescription)
|
||||
}
|
||||
|
|
89
internal/notif/ntfy.go
Normal file
89
internal/notif/ntfy.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package notif
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
E "github.com/yusing/go-proxy/internal/error"
|
||||
)
|
||||
|
||||
// See https://docs.ntfy.sh/publish
|
||||
type Ntfy struct {
|
||||
ProviderBase
|
||||
Topic string `json:"topic"`
|
||||
Style NtfyStyle `json:"style"`
|
||||
}
|
||||
|
||||
type NtfyStyle string
|
||||
|
||||
const (
|
||||
NtfyStyleMarkdown NtfyStyle = "markdown"
|
||||
NtfyStylePlain NtfyStyle = "plain"
|
||||
)
|
||||
|
||||
func (n *Ntfy) Validate() E.Error {
|
||||
if n.URL == "" {
|
||||
return E.New("url is required")
|
||||
}
|
||||
if n.Topic == "" {
|
||||
return E.New("topic is required")
|
||||
}
|
||||
if n.Topic[0] == '/' {
|
||||
return E.New("topic should not start with a slash")
|
||||
}
|
||||
switch n.Style {
|
||||
case "":
|
||||
n.Style = NtfyStyleMarkdown
|
||||
case NtfyStyleMarkdown, NtfyStylePlain:
|
||||
default:
|
||||
return E.Errorf("invalid style, expecting %q or %q, got %q", NtfyStyleMarkdown, NtfyStylePlain, n.Style)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Ntfy) GetURL() string {
|
||||
if n.URL[len(n.URL)-1] == '/' {
|
||||
return n.URL + n.Topic
|
||||
}
|
||||
return n.URL + "/" + n.Topic
|
||||
}
|
||||
|
||||
func (n *Ntfy) GetMIMEType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (n *Ntfy) GetToken() string {
|
||||
return n.Token
|
||||
}
|
||||
|
||||
func (n *Ntfy) MakeBody(logMsg *LogMessage) (io.Reader, error) {
|
||||
switch n.Style {
|
||||
case NtfyStyleMarkdown:
|
||||
return strings.NewReader(formatMarkdown(logMsg.Extras)), nil
|
||||
default:
|
||||
return &bytes.Buffer{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Ntfy) SetHeaders(logMsg *LogMessage, headers http.Header) {
|
||||
headers.Set("Title", logMsg.Title)
|
||||
|
||||
switch logMsg.Level {
|
||||
// warning (or other unspecified) uses default priority
|
||||
case zerolog.FatalLevel:
|
||||
headers.Set("Priority", "urgent")
|
||||
case zerolog.ErrorLevel:
|
||||
headers.Set("Priority", "high")
|
||||
case zerolog.InfoLevel:
|
||||
headers.Set("Priority", "low")
|
||||
case zerolog.DebugLevel:
|
||||
headers.Set("Priority", "min")
|
||||
}
|
||||
|
||||
if n.Style == NtfyStyleMarkdown {
|
||||
headers.Set("Markdown", "yes")
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ type (
|
|||
GetMIMEType() string
|
||||
|
||||
MakeBody(logMsg *LogMessage) (io.Reader, error)
|
||||
SetHeaders(logMsg *LogMessage, headers http.Header)
|
||||
|
||||
makeRespError(resp *http.Response) error
|
||||
}
|
||||
|
@ -30,6 +31,7 @@ type (
|
|||
|
||||
const (
|
||||
ProviderGotify = "gotify"
|
||||
ProviderNtfy = "ntfy"
|
||||
ProviderWebhook = "webhook"
|
||||
)
|
||||
|
||||
|
@ -52,6 +54,7 @@ func notifyProvider(ctx context.Context, provider Provider, msg *LogMessage) err
|
|||
if provider.GetToken() != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+provider.GetToken())
|
||||
}
|
||||
provider.SetHeaders(msg, req.Header)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
|
|
|
@ -92,12 +92,12 @@ func (webhook *Webhook) GetMIMEType() string {
|
|||
func (webhook *Webhook) makeRespError(resp *http.Response) error {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("webhook status %d, failed to read body: %w", resp.StatusCode, err)
|
||||
return fmt.Errorf("%s status %d, failed to read body: %w", webhook.Name, resp.StatusCode, err)
|
||||
}
|
||||
if len(body) > 0 {
|
||||
return fmt.Errorf("webhook status %d: %s", resp.StatusCode, body)
|
||||
return fmt.Errorf("%s status %d: %s", webhook.Name, resp.StatusCode, body)
|
||||
}
|
||||
return fmt.Errorf("webhook status %d", resp.StatusCode)
|
||||
return fmt.Errorf("%s status %d", webhook.Name, resp.StatusCode)
|
||||
}
|
||||
|
||||
func (webhook *Webhook) MakeBody(logMsg *LogMessage) (io.Reader, error) {
|
||||
|
|
|
@ -217,14 +217,14 @@ func (mon *monitor) checkUpdateHealth() error {
|
|||
notif.Notify(¬if.LogMessage{
|
||||
Title: "✅ Service is up ✅",
|
||||
Extras: extras,
|
||||
Color: notif.Green,
|
||||
Color: notif.ColorSuccess,
|
||||
})
|
||||
} else {
|
||||
logger.Warn().Msg("service went down")
|
||||
notif.Notify(¬if.LogMessage{
|
||||
Title: "❌ Service went down ❌",
|
||||
Extras: extras,
|
||||
Color: notif.Red,
|
||||
Color: notif.ColorError,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
12
schemas/config/notification.d.ts
vendored
12
schemas/config/notification.d.ts
vendored
|
@ -1,5 +1,5 @@
|
|||
import { URL } from "../types";
|
||||
export declare const NOTIFICATION_PROVIDERS: readonly ["webhook", "gotify"];
|
||||
export declare const NOTIFICATION_PROVIDERS: readonly ["webhook", "gotify", "ntfy"];
|
||||
export type NotificationProvider = (typeof NOTIFICATION_PROVIDERS)[number];
|
||||
export type NotificationConfig = {
|
||||
name: string;
|
||||
|
@ -9,9 +9,17 @@ export interface GotifyConfig extends NotificationConfig {
|
|||
provider: "gotify";
|
||||
token: string;
|
||||
}
|
||||
export declare const NTFY_MSG_STYLES: string[];
|
||||
export type NtfyStyle = (typeof NTFY_MSG_STYLES)[number];
|
||||
export interface NtfyConfig extends NotificationConfig {
|
||||
provider: "ntfy";
|
||||
topic: string;
|
||||
token?: string;
|
||||
style?: NtfyStyle;
|
||||
}
|
||||
export declare const WEBHOOK_TEMPLATES: readonly ["", "discord"];
|
||||
export declare const WEBHOOK_METHODS: readonly ["POST", "GET", "PUT"];
|
||||
export declare const WEBHOOK_MIME_TYPES: readonly ["application/json", "application/x-www-form-urlencoded", "text/plain"];
|
||||
export declare const WEBHOOK_MIME_TYPES: readonly ["application/json", "application/x-www-form-urlencoded", "text/plain", "text/markdown"];
|
||||
export declare const WEBHOOK_COLOR_MODES: readonly ["hex", "dec"];
|
||||
export type WebhookTemplate = (typeof WEBHOOK_TEMPLATES)[number];
|
||||
export type WebhookMethod = (typeof WEBHOOK_METHODS)[number];
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
export const NOTIFICATION_PROVIDERS = ["webhook", "gotify"];
|
||||
export const NOTIFICATION_PROVIDERS = ["webhook", "gotify", "ntfy"];
|
||||
export const NTFY_MSG_STYLES = ["markdown", "plain"];
|
||||
export const WEBHOOK_TEMPLATES = ["", "discord"];
|
||||
export const WEBHOOK_METHODS = ["POST", "GET", "PUT"];
|
||||
export const WEBHOOK_MIME_TYPES = [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"text/plain",
|
||||
"text/markdown",
|
||||
];
|
||||
export const WEBHOOK_COLOR_MODES = ["hex", "dec"];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { URL } from "../types";
|
||||
|
||||
export const NOTIFICATION_PROVIDERS = ["webhook", "gotify"] as const;
|
||||
export const NOTIFICATION_PROVIDERS = ["webhook", "gotify", "ntfy"] as const;
|
||||
|
||||
export type NotificationProvider = (typeof NOTIFICATION_PROVIDERS)[number];
|
||||
|
||||
|
@ -17,12 +17,23 @@ export interface GotifyConfig extends NotificationConfig {
|
|||
token: string;
|
||||
}
|
||||
|
||||
export const NTFY_MSG_STYLES = ["markdown", "plain"];
|
||||
export type NtfyStyle = (typeof NTFY_MSG_STYLES)[number];
|
||||
|
||||
export interface NtfyConfig extends NotificationConfig {
|
||||
provider: "ntfy";
|
||||
topic: string;
|
||||
token?: string;
|
||||
style?: NtfyStyle;
|
||||
}
|
||||
|
||||
export const WEBHOOK_TEMPLATES = ["", "discord"] as const;
|
||||
export const WEBHOOK_METHODS = ["POST", "GET", "PUT"] as const;
|
||||
export const WEBHOOK_MIME_TYPES = [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"text/plain",
|
||||
"text/markdown",
|
||||
] as const;
|
||||
export const WEBHOOK_COLOR_MODES = ["hex", "dec"] as const;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue