Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ notification:
url: <your gotify url> # Your Gotify server URL
token: <your gotify api token> # Application token from Gotify

pushover:
token: <your pushover api token> # Application API token from Pushover
user: <your pushover user key> # Your user key from Pushover
# Optional settings:
# device: <your device name> # 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)
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documented priority range includes "2=emergency" but the validation in config/notification.go only allows -2 to 1. Either update the validation to support priority 2 (which would also require adding retry and expire parameters per Pushover's API), or correct the documentation to match the current implementation by removing "2=emergency" from this comment.

Suggested change
# priority: 0 # Message priority (-2=lowest, -1=low, 0=normal, 1=high, 2=emergency)
# priority: 0 # Message priority (-2=lowest, -1=low, 0=normal, 1=high)

Copilot uses AI. Check for mistakes.

# Global ticket configuration
# All available settings are outlined below
# These settings apply to all tickets by default
Expand Down
83 changes: 53 additions & 30 deletions config/config.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions config/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
return nil
}

func (c NotificationConfig) Validate() error {

Check failure on line 41 in config/notification.go

View workflow job for this annotation

GitHub Actions / Lint / Lint Backend

cyclomatic: function (NotificationConfig).Validate has cyclomatic complexity 16 (> max enabled 12) (revive)
if c.Ntfy != nil {
if !beginsWithHttp(c.Ntfy.Url) {
return errors.New("ntfy url must begin with 'http://' or 'https://'")
Expand Down Expand Up @@ -66,6 +66,21 @@
}
}

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
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
9 changes: 9 additions & 0 deletions notification/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
60 changes: 60 additions & 0 deletions notification/pushover.go
Original file line number Diff line number Diff line change
@@ -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) {

Check failure on line 51 in notification/pushover.go

View workflow job for this annotation

GitHub Actions / Lint / Lint Backend

NewPushoverClient - result 1 (error) is always nil (unparam)
app := pushover.New(conf.Token)
recipient := pushover.NewRecipient(conf.User)

return PushoverClient{
app: app,
recipient: recipient,
config: conf,
}, nil
}
60 changes: 60 additions & 0 deletions notification/pushover_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
31 changes: 31 additions & 0 deletions schema/models.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ components:
telegram:
x-order: 3
$ref: "#/components/schemas/TelegramConfig"
pushover:
x-order: 4
$ref: "#/components/schemas/PushoverConfig"

NtfyConfig:
type: object
Expand Down Expand Up @@ -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: |
Expand Down Expand Up @@ -318,3 +348,4 @@ components:
- ntfy
- gotify
- telegram
- pushover
Loading
Loading