Skip to content

Commit 68d208d

Browse files
committed
feat(notifications): add suppress-for notification-uri config option
1 parent 3a7e9b3 commit 68d208d

File tree

3 files changed

+64
-23
lines changed

3 files changed

+64
-23
lines changed

cmd/internal/wrtagflag/wrtagflag.go

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,28 @@ func (r researchLinkParser) String() string {
161161
type notificationsParser struct{ *notifications.Notifications }
162162

163163
func (n *notificationsParser) Set(value string) error {
164-
eventsRaw, uri, ok := strings.Cut(value, " ")
165-
if !ok {
166-
return errors.New("invalid notification uri format. expected eg \"ev1,ev2 uri\"")
164+
parts := strings.Fields(value)
165+
if len(parts) < 2 {
166+
return errors.New("invalid notification uri format. expected eg \"ev1,ev2 uri [suppress-for 30s]\"")
167+
}
168+
eventsRaw, uri := parts[0], parts[1]
169+
170+
var suppressFor time.Duration
171+
if len(parts) >= 4 && parts[2] == "suppress-for" {
172+
var err error
173+
suppressFor, err = time.ParseDuration(parts[3])
174+
if err != nil {
175+
return fmt.Errorf("parse suppress duration: %w", err)
176+
}
167177
}
178+
168179
var lineErrs []error
169180
for ev := range strings.SplitSeq(eventsRaw, ",") {
170-
ev, uri = strings.TrimSpace(ev), strings.TrimSpace(uri)
171-
err := n.AddURI(ev, uri)
172-
lineErrs = append(lineErrs, err)
181+
ev = strings.TrimSpace(ev)
182+
if err := n.AddDestination(ev, uri, suppressFor); err != nil {
183+
lineErrs = append(lineErrs, err)
184+
continue
185+
}
173186
}
174187
return errors.Join(lineErrs...)
175188
}
@@ -178,10 +191,14 @@ func (n notificationsParser) String() string {
178191
return ""
179192
}
180193
var parts []string
181-
n.Notifications.IterMappings(func(e string, uri string) {
182-
url, _ := url.Parse(uri)
183-
parts = append(parts, fmt.Sprintf("%s: %s://%s/...", e, url.Scheme, url.Host))
184-
})
194+
for ev, dest := range n.Notifications.Destinations() {
195+
uri, _ := url.Parse(dest.URI)
196+
part := fmt.Sprintf("%s: %s://%s/...", ev, uri.Scheme, uri.Host)
197+
if dest.SuppressFor > 0 {
198+
part += fmt.Sprintf(" (suppress for %s)", dest.SuppressFor)
199+
}
200+
parts = append(parts, part)
201+
}
185202
return strings.Join(parts, ", ")
186203
}
187204

config.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@
3636
# add notification uri that are triggered by various events
3737
# see all available services here https://containrrr.dev/shoutrrr/v0.8/services/generic/
3838
# possible events are complete, needs-input, sync-complete, sync-error
39+
# optional "suppress-for" prevents notifications for specified duration after manual actions
3940

40-
#notification-uri complete,needs-input,sync-error smtp://username:password@host:port/?from=from@example.com&to=recipient@example.com
41+
#notification-uri complete,needs-input,sync-error smtp://username:password@host:port/?from=from@example.com&to=recipient@example.com suppress-for 30s
4142
#notification-uri complete,sync-complete generic+https://my.subsonic.com/rest/startScan.view?c=wrtag&v=1.16&u=user&p=password
4243

4344
# set a list of files to keep when moving or copying

notifications/notifications.go

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"iter"
78
"log/slog"
9+
"maps"
810
"net/url"
911
"time"
1012

@@ -16,42 +18,63 @@ var (
1618
ErrInvalidURI = errors.New("invalid URI")
1719
)
1820

21+
type Destination struct {
22+
URI string
23+
SuppressFor time.Duration // wait this long after manual actions before sending
24+
}
25+
1926
type Notifications struct {
20-
mappings map[string][]string
27+
mappings map[string][]Destination
2128
}
2229

23-
func (n *Notifications) AddURI(event string, uri string) error {
30+
func (n *Notifications) AddDestination(event string, uri string, suppressFor time.Duration) error {
2431
if n.mappings == nil {
25-
n.mappings = map[string][]string{}
32+
n.mappings = map[string][]Destination{}
2633
}
2734
if _, err := url.Parse(uri); err != nil {
2835
return fmt.Errorf("parse uri: %w", err)
2936
}
30-
n.mappings[event] = append(n.mappings[event], uri)
37+
n.mappings[event] = append(n.mappings[event], Destination{URI: uri, SuppressFor: suppressFor})
3138
return nil
3239
}
3340

34-
func (n *Notifications) IterMappings(f func(string, string)) {
35-
for event, uris := range n.mappings {
36-
for _, uri := range uris {
37-
f(event, uri)
41+
func (n *Notifications) Destinations() iter.Seq2[string, Destination] {
42+
mappings := maps.Clone(n.mappings)
43+
44+
return func(yield func(string, Destination) bool) {
45+
for event, destinations := range mappings {
46+
for _, destination := range destinations {
47+
if !yield(event, destination) {
48+
return
49+
}
50+
}
3851
}
3952
}
4053
}
54+
4155
func (n *Notifications) Sendf(ctx context.Context, event string, f string, a ...any) {
4256
n.Send(ctx, event, fmt.Sprintf(f, a...))
4357
}
4458

4559
// Send a simple string for now, maybe later message could instead be a type which
4660
// implements a notifications.Bodyer or something so that notifiers can send rich notifications.
4761
func (n *Notifications) Send(ctx context.Context, event string, message string) {
48-
if actionTime, ok := ctx.Value(actionKey{}).(time.Time); ok && time.Since(actionTime) < 30*time.Second {
49-
slog.DebugContext(ctx, "suppressing notification for recent manual action",
50-
"event", event, "since_manual", time.Since(actionTime))
62+
destinations := n.mappings[event]
63+
if len(destinations) == 0 {
5164
return
5265
}
5366

54-
uris := n.mappings[event]
67+
var timeSinceAction time.Duration
68+
if actionTime, ok := ctx.Value(actionKey{}).(time.Time); ok {
69+
timeSinceAction = time.Since(actionTime)
70+
}
71+
72+
var uris []string
73+
for _, dest := range destinations {
74+
if timeSinceAction == 0 || timeSinceAction >= dest.SuppressFor {
75+
uris = append(uris, dest.URI)
76+
}
77+
}
5578
if len(uris) == 0 {
5679
return
5780
}

0 commit comments

Comments
 (0)