-
Notifications
You must be signed in to change notification settings - Fork 0
/
email.go
214 lines (178 loc) · 6.32 KB
/
email.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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
package output
import (
"bytes"
"encoding/base64"
"fmt"
"html/template"
"net/http"
"path/filepath"
"strings"
"time"
"github.com/ggiammat/mattermost-missed-activity-notifier/server/backend"
"github.com/ggiammat/mattermost-missed-activity-notifier/server/model"
"github.com/pkg/errors"
)
type EmailTemplateProps struct {
SubTitle string
ButtonText string
FooterLine1 string
FooterLine2 string
FooterLine3 string
}
type postData struct {
SenderName string
ChannelName string
Message template.HTML
SenderPhoto template.URL
PostPhoto string
Time string
ShowChannelIcon bool
OtherChannelMembersCount int
MessageAttachments []*string
Link template.URL
AlreadyRead bool
}
type conversationData struct {
RootPost postData
Replies []postData
NumReplies int
}
type channelData struct {
ChannelName string
ShowChannelIcon bool
OtherChannelMembersCount int
Conversations []*conversationData
NumRepliesInNotFollowedThreads int
NumNotifiedByMM int
NumPreviouslyNotified int
}
func BuildChannelData(cma *model.ChannelMissedActivity, conversationsData []*conversationData) *channelData {
return &channelData{
ChannelName: cma.GetChannelName(),
ShowChannelIcon: true,
NumRepliesInNotFollowedThreads: cma.RepliesInNotFollowingConvs,
NumNotifiedByMM: cma.NotifiedByMMMessages,
NumPreviouslyNotified: cma.PreviouslyNotified,
Conversations: conversationsData,
}
}
type templateData struct {
Props map[string]any
HTML map[string]string
}
func formatMessage(message string) template.HTML {
return template.HTML(strings.Replace(template.HTMLEscapeString(message), "\n", "<br>", -1))
}
func toBase64(bytes []byte) template.URL {
var base64Encoding string
// Determine the content type of the image file
mimeType := http.DetectContentType(bytes)
// Prepend the appropriate URI scheme header depending
// on the MIME type. The full scheme would be "data:image/jpeg;base64,"
// but we remove the first part "data:image/" (and add it in the template
// directly) because it is considered unsafe by html/template and would
// not be rendered in the template.
switch mimeType {
case "image/jpeg":
base64Encoding += "data:image/jpeg;base64,"
case "image/png":
base64Encoding += "data:image/png;base64,"
}
// Append the base64 encoded output
base64Encoding += base64.StdEncoding.EncodeToString(bytes)
return template.URL(base64Encoding)
}
func BuildHTMLEmail(backend *backend.MattermostBackend, missedActivity *model.TeamMissedActivity, props *EmailTemplateProps) (string, string, error) {
serverName := backend.GetServerName()
serverURL := backend.GetServerURL()
// closure function to build the link to messages
buildMessageLink := func(post *model.Post) template.URL {
teamName := missedActivity.Team.Name
if missedActivity.Team.Id == "" {
// although direct messages don't belong to any team, we have to specify a team name in the
// url. We choose the first team the user belongs to
teams, _ := backend.GetTeamsForUser(missedActivity.User.Id)
if len(teams) < 1 {
backend.LogError("Cannot build a link for direct message %s: user is not member of any team", post.Id)
return template.URL(serverURL)
}
teamName = teams[0].Name
}
return template.URL(fmt.Sprintf("%s/%s/pl/%s", serverURL, strings.ToLower(teamName), post.Id))
}
t, err := template.ParseFiles(filepath.Join(backend.GetTemplatesPath(), "email-content.html"))
if err != nil {
return "", "", errors.Wrap(err, "Error loading template")
}
title := fmt.Sprintf("Missed Activity in the %s team", missedActivity.Team.Name)
if missedActivity.Team == model.DIRECT_MESSAGES_FAKE_TEAM {
title = fmt.Sprintf("Missed Direct Messages in %s", serverName)
}
data := templateData{
Props: map[string]any{
"SiteURL": serverURL,
"EmailTitle": title,
"ButtonURL": serverURL,
"EmailSubTitle": props.SubTitle,
"EmailButton": props.ButtonText,
"EmailFooterLine1": props.FooterLine1,
"EmailFooterLine2": props.FooterLine2,
"EmailFooterLine3": props.FooterLine3,
},
HTML: map[string]string{},
}
nConversations := 0
channels := []*channelData{}
for _, cma := range missedActivity.UnreadChannels {
conversationsData := []*conversationData{}
for _, conv := range cma.UnreadConversations {
author, _ := backend.GetUser(conv.RootPost.AuthorId)
p := postData{
SenderName: author.DisplayName(),
Message: formatMessage(conv.RootPost.Message),
Time: conv.RootPost.CreatedAt.Format(time.RFC822),
SenderPhoto: toBase64(author.Image),
Link: buildMessageLink(conv.RootPost),
AlreadyRead: !conv.IsRootMessageUnread,
}
cv := &conversationData{RootPost: p}
replies := []postData{}
for _, rep := range conv.Replies {
author, _ := backend.GetUser(rep.AuthorId)
p := postData{
SenderName: author.DisplayName(),
Message: formatMessage(rep.Message),
Time: rep.CreatedAt.Format(time.RFC822),
SenderPhoto: toBase64(author.Image),
Link: buildMessageLink(rep),
}
replies = append(replies, p)
}
cv.Replies = replies
cv.NumReplies = len(cv.Replies)
conversationsData = append(conversationsData, cv)
nConversations++
}
// only add a channel if there is something to notify, otherwise an empty header will appear in the email
if len(conversationsData) > 0 || cma.RepliesInNotFollowingConvs > 0 || cma.NotifiedByMMMessages > 0 || cma.PreviouslyNotified > 0 {
channels = append(channels, BuildChannelData(&cma, conversationsData))
}
}
data.Props["Channels"] = channels
// build email only if there is at least one conversation in one channel
if nConversations > 0 {
w := new(bytes.Buffer)
errR := t.Execute(w, data)
if errR != nil {
return "", "", errors.Wrap(errR, "Error rendering html email template")
}
var subject string
if missedActivity.Team.Id != "" {
subject = fmt.Sprintf("[%s] Recent activity in %s", serverName, missedActivity.Team.Name)
} else {
subject = fmt.Sprintf("[%s] Unread direct messages", serverName)
}
return subject, w.String(), nil
}
return "", "", nil
}