From 8a428211b8304ab39401ff0025f22d5569c0d85c Mon Sep 17 00:00:00 2001 From: George Thompson Date: Sun, 28 Dec 2025 15:40:59 +0000 Subject: [PATCH] Add Pushover notification service Adds optional Pushover notification support following existing patterns. - PushoverConfig with token, user, device, sound, priority fields - Priority range -2 to 1 (lowest to high) - Client using gregdel/pushover library - Integration test (skipped by default) - Example configuration --- config.example.yaml | 8 ++++ config/config.gen.go | 83 ++++++++++++++++++++++------------- config/notification.go | 15 +++++++ go.mod | 1 + go.sum | 2 + notification/notification.go | 9 ++++ notification/pushover.go | 60 +++++++++++++++++++++++++ notification/pushover_test.go | 60 +++++++++++++++++++++++++ schema/models.openapi.yaml | 31 +++++++++++++ server/server.gen.go | 71 ++++++++++++++++-------------- 10 files changed, 276 insertions(+), 64 deletions(-) create mode 100644 notification/pushover.go create mode 100644 notification/pushover_test.go diff --git a/config.example.yaml b/config.example.yaml index 9785ab4..7d67060 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -21,6 +21,14 @@ notification: url: # Your Gotify server URL token: # Application token from Gotify + pushover: + token: # Application API token from Pushover + user: # Your user key from Pushover + # Optional settings: + # device: # Target specific device + # sound: pushover # Notification sound (pushover, bike, bugle, cosmic, etc.) + # priority: 0 # Message priority (-2=lowest, -1=low, 0=normal, 1=high, 2=emergency) + # Global ticket configuration # All available settings are outlined below # These settings apply to all tickets by default diff --git a/config/config.gen.go b/config/config.gen.go index 95be260..7eeb7e6 100644 --- a/config/config.gen.go +++ b/config/config.gen.go @@ -23,6 +23,7 @@ var ( NotificationTypeGotify = notificationTypeBuilder.Add(NotificationType{"gotify"}) NotificationTypeNtfy = notificationTypeBuilder.Add(NotificationType{"ntfy"}) + NotificationTypePushover = notificationTypeBuilder.Add(NotificationType{"pushover"}) NotificationTypeTelegram = notificationTypeBuilder.Add(NotificationType{"telegram"}) NotificationTypes = notificationTypeBuilder.Enum() @@ -94,6 +95,7 @@ type NotificationConfig struct { Ntfy *NtfyConfig `json:"ntfy,omitempty"` Gotify *GotifyConfig `json:"gotify,omitempty"` Telegram *TelegramConfig `json:"telegram,omitempty"` + Pushover *PushoverConfig `json:"pushover,omitempty"` } // NotificationType defines model for NotificationType. @@ -117,6 +119,24 @@ type NtfyConfig struct { Password string `json:"password,omitempty"` } +// PushoverConfig defines model for PushoverConfig. +type PushoverConfig struct { + // Token Application API token from Pushover + Token string `json:"token"` + + // User Your user key from Pushover + User string `json:"user"` + + // Device Optional: specific device name to send to + Device *string `json:"device,omitempty"` + + // Sound Optional: notification sound (see Pushover docs for valid sounds) + Sound *string `json:"sound,omitempty"` + + // Priority Optional: message priority (-2 to 1, default 0). -2=lowest, -1=low, 0=normal, 1=high + Priority *int `json:"priority,omitempty"` +} + // Region Region code. // Possible values are: // - GBLO: London @@ -183,36 +203,39 @@ type TicketListingConfig struct { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/config/notification.go b/config/notification.go index d534a6a..5cb8449 100644 --- a/config/notification.go +++ b/config/notification.go @@ -66,6 +66,21 @@ func (c NotificationConfig) Validate() error { } } + if c.Pushover != nil { + if c.Pushover.Token == "" { + return errors.New("pushover token cannot be empty") + } + if c.Pushover.User == "" { + return errors.New("pushover user cannot be empty") + } + if c.Pushover.Priority != nil { + priority := *c.Pushover.Priority + if priority < -2 || priority > 1 { + return errors.New("pushover priority must be between -2 and 1") + } + } + } + return nil } diff --git a/go.mod b/go.mod index d223c9a..aecd4d4 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 github.com/goccy/go-yaml v1.18.0 github.com/gotify/go-api-client/v2 v2.0.4 + github.com/gregdel/pushover v1.4.0 github.com/joho/godotenv v1.5.1 github.com/knadh/koanf v1.5.0 github.com/mitchellh/mapstructure v1.5.0 diff --git a/go.sum b/go.sum index 08b592e..e1fdf1e 100644 --- a/go.sum +++ b/go.sum @@ -441,6 +441,8 @@ github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+ github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= github.com/gotify/go-api-client/v2 v2.0.4 h1:0w8skCr8aLBDKaQDg31LKKHUGF7rt7zdRpR+6cqIAlE= github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU= +github.com/gregdel/pushover v1.4.0 h1:P77WAJ2zPG+b0mEsmMjWGrPMuvhkh9k3v7OviwsoveE= +github.com/gregdel/pushover v1.4.0/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ= diff --git a/notification/notification.go b/notification/notification.go index c476170..446965f 100644 --- a/notification/notification.go +++ b/notification/notification.go @@ -152,5 +152,14 @@ func GetNotificationClients(conf config.NotificationConfig) (map[config.Notifica clients[config.NotificationTypeTelegram] = telegramClient } + if conf.Pushover != nil { + pushoverClient, err := NewPushoverClient(*conf.Pushover) + if err != nil { + return nil, fmt.Errorf("failed to setup pushover client: %w", err) + } + + clients[config.NotificationTypePushover] = pushoverClient + } + return clients, nil } diff --git a/notification/pushover.go b/notification/pushover.go new file mode 100644 index 0000000..4f6c6f0 --- /dev/null +++ b/notification/pushover.go @@ -0,0 +1,60 @@ +package notification + +import ( + "fmt" + + "github.com/ahobsonsayers/twigots" + "github.com/ahobsonsayers/twitchets/config" + "github.com/gregdel/pushover" +) + +type PushoverClient struct { + app *pushover.Pushover + recipient *pushover.Recipient + config config.PushoverConfig +} + +var _ Client = PushoverClient{} + +func (p PushoverClient) SendTicketNotification(ticket twigots.TicketListing) error { + notificationMessage, err := RenderMessage(ticket) + if err != nil { + return err + } + + message := pushover.NewMessage(notificationMessage) + message.Title = ticket.Event.Name + message.URL = ticket.URL() + message.URLTitle = "Buy Tickets" + + // Apply optional configuration + if p.config.Device != nil && *p.config.Device != "" { + message.DeviceName = *p.config.Device + } + + if p.config.Sound != nil && *p.config.Sound != "" { + message.Sound = *p.config.Sound + } + + if p.config.Priority != nil { + message.Priority = *p.config.Priority + } + + _, err = p.app.SendMessage(message, p.recipient) + if err != nil { + return fmt.Errorf("failed to send pushover notification: %w", err) + } + + return nil +} + +func NewPushoverClient(conf config.PushoverConfig) (PushoverClient, error) { + app := pushover.New(conf.Token) + recipient := pushover.NewRecipient(conf.User) + + return PushoverClient{ + app: app, + recipient: recipient, + config: conf, + }, nil +} diff --git a/notification/pushover_test.go b/notification/pushover_test.go new file mode 100644 index 0000000..d3489c6 --- /dev/null +++ b/notification/pushover_test.go @@ -0,0 +1,60 @@ +package notification_test + +import ( + "os" + "strconv" + "testing" + + "github.com/ahobsonsayers/twitchets/config" + "github.com/ahobsonsayers/twitchets/notification" + "github.com/ahobsonsayers/twitchets/test" + "github.com/joho/godotenv" + "github.com/stretchr/testify/require" +) + +func TestPushoverSendTicketMessage(t *testing.T) { + t.Skip("Can only be run manually locally with environment variables set. Comment to run.") + + _ = godotenv.Load(test.ProjectDirectoryJoin(t, ".env")) + + pushoverToken := os.Getenv("PUSHOVER_TOKEN") + require.NotEmpty(t, pushoverToken, "PUSHOVER_TOKEN is not set") + + pushoverUser := os.Getenv("PUSHOVER_USER") + require.NotEmpty(t, pushoverUser, "PUSHOVER_USER is not set") + + // Optional: test with device + pushoverDevice := os.Getenv("PUSHOVER_DEVICE") + var devicePtr *string + if pushoverDevice != "" { + devicePtr = &pushoverDevice + } + + // Optional: test with sound + pushoverSound := os.Getenv("PUSHOVER_SOUND") + var soundPtr *string + if pushoverSound != "" { + soundPtr = &pushoverSound + } + + // Optional: test with priority + var priorityPtr *int + if pushoverPriorityStr := os.Getenv("PUSHOVER_PRIORITY"); pushoverPriorityStr != "" { + priority, err := strconv.Atoi(pushoverPriorityStr) + require.NoError(t, err, "PUSHOVER_PRIORITY is not an integer") + priorityPtr = &priority + } + + client, err := notification.NewPushoverClient(config.PushoverConfig{ + Token: pushoverToken, + User: pushoverUser, + Device: devicePtr, + Sound: soundPtr, + Priority: priorityPtr, + }) + require.NoError(t, err) + + ticket := testNotificationTicket() + err = client.SendTicketNotification(ticket) + require.NoError(t, err) +} diff --git a/schema/models.openapi.yaml b/schema/models.openapi.yaml index a021463..bd46187 100644 --- a/schema/models.openapi.yaml +++ b/schema/models.openapi.yaml @@ -63,6 +63,9 @@ components: telegram: x-order: 3 $ref: "#/components/schemas/TelegramConfig" + pushover: + x-order: 4 + $ref: "#/components/schemas/PushoverConfig" NtfyConfig: type: object @@ -119,6 +122,33 @@ components: - token - chatId + PushoverConfig: + type: object + properties: + token: + x-order: 1 + description: Application API token from Pushover + type: string + user: + x-order: 2 + description: Your user key from Pushover + type: string + device: + x-order: 3 + description: "Optional: specific device name to send to" + type: string + sound: + x-order: 4 + description: "Optional: notification sound (see Pushover docs for valid sounds)" + type: string + priority: + x-order: 5 + description: "Optional: message priority (-2 to 1, default 0). -2=lowest, -1=low, 0=normal, 1=high" + type: integer + required: + - token + - user + GlobalTicketListingConfig: type: object description: | @@ -318,3 +348,4 @@ components: - ntfy - gotify - telegram + - pushover diff --git a/server/server.gen.go b/server/server.gen.go index 9e8fc1c..3451200 100644 --- a/server/server.gen.go +++ b/server/server.gen.go @@ -363,40 +363,43 @@ func (sh *strictHandler) PutConfig(w http.ResponseWriter, r *http.Request) { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file