-
Notifications
You must be signed in to change notification settings - Fork 5
/
notification-service.go
156 lines (143 loc) · 4.45 KB
/
notification-service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package core
import (
"fmt"
"log"
"strings"
"time"
niotify "gioui.org/x/notify"
"git.sr.ht/~whereswaldon/forest-go"
"git.sr.ht/~whereswaldon/forest-go/store"
)
// NotificationService provides methods to send notifications and to
// configure notifications for collections of arbor nodes.
type NotificationService interface {
Register(store.ExtendedStore)
Notify(title, content string) error
}
// notificationManager implements NotificationService and provides
// methods to send notifications and choose (based on settings)
// whether to notify for a given arbor message.
type notificationManager struct {
SettingsService
ArborService
niotify.Notifier
TimeLaunched uint64
}
var _ NotificationService = ¬ificationManager{}
// newNotificationService constructs a new NotificationService for the
// provided App.
func newNotificationService(settings SettingsService, arbor ArborService) (NotificationService, error) {
m, err := niotify.NewNotifier()
if err != nil {
return nil, fmt.Errorf("failed initializing notification support: %w", err)
}
return ¬ificationManager{
SettingsService: settings,
ArborService: arbor,
Notifier: m,
TimeLaunched: uint64(time.Now().UnixNano() / 1000000),
}, nil
}
// Register configures the store so that new nodes will generate notifications
// if notifications are appropriate (based on current user settings).
func (n *notificationManager) Register(s store.ExtendedStore) {
s.SubscribeToNewMessages(n.handleNode)
}
// shouldNotify returns whether or not a node should generate a notification
// according to the user's current settings.
func (n *notificationManager) shouldNotify(reply *forest.Reply) bool {
if !n.SettingsService.NotificationsGloballyAllowed() {
return false
}
if md, err := reply.TwigMetadata(); err != nil || md.Contains("invisible", 1) {
// Invisible message
return false
}
localUserID := n.SettingsService.ActiveArborIdentityID()
if localUserID == nil {
return false
}
localUserNode, has, err := n.ArborService.Store().GetIdentity(localUserID)
if err != nil || !has {
return false
}
twigData, err := reply.TwigMetadata()
if err != nil {
log.Printf("Error checking whether to notify while parsing twig metadata for node %s", reply.ID())
} else {
if twigData.Contains("invisible", 1) {
return false
}
}
localUser := localUserNode.(*forest.Identity)
messageContent := strings.ToLower(string(reply.Content.Blob))
username := strings.ToLower(string(localUser.Name.Blob))
if strings.Contains(messageContent, username) {
// local user directly mentioned
return true
}
if uint64(reply.Created) < n.TimeLaunched {
// do not send old notifications
return false
}
if reply.Author.Equals(localUserID) {
// Do not send notifications for replies created by the local
// user's identity.
return false
}
if reply.TreeDepth() == 1 {
// Notify of new conversation
return true
}
parent, known, err := n.ArborService.Store().Get(reply.ParentID())
if err != nil || !known {
// Don't notify if we don't know about this conversation.
return false
}
if parent.(*forest.Reply).Author.Equals(localUserID) {
// Direct response to local user.
return true
}
return false
}
// Notify sends a notification with the given title and content if
// notifications are currently allowed.
func (n *notificationManager) Notify(title, content string) error {
if !n.SettingsService.NotificationsGloballyAllowed() {
return nil
}
_, err := n.CreateNotification(title, content)
if err != nil {
return fmt.Errorf("failed to create notification: %w", err)
}
return nil
}
// handleNode spawns a worker goroutine to decide whether or not
// to notify for a given node. This makes it appropriate as a subscriber
// function on a store.ExtendedStore, as it will not block.
func (n *notificationManager) handleNode(node forest.Node) {
if asReply, ok := node.(*forest.Reply); ok {
go func(reply *forest.Reply) {
if !n.shouldNotify(reply) {
return
}
var title, authorName string
author, _, err := n.ArborService.Store().GetIdentity(&reply.Author)
if err != nil {
authorName = "???"
} else {
authorName = string(author.(*forest.Identity).Name.Blob)
}
switch {
case reply.Depth == 1:
title = fmt.Sprintf("New conversation by %s", authorName)
default:
title = fmt.Sprintf("New reply from %s", authorName)
}
err = n.Notify(title, string(reply.Content.Blob))
if err != nil {
log.Printf("failed sending notification: %v", err)
}
}(asReply)
}
}