-
Notifications
You must be signed in to change notification settings - Fork 26
/
messagecard.go
324 lines (265 loc) · 11.3 KB
/
messagecard.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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
package goteamsnotify
import (
"errors"
"fmt"
"strings"
)
// MessageCardSectionFact represents a section fact entry that is usually
// displayed in a two-column key/value format.
type MessageCardSectionFact struct {
// Name is the key for an associated value in a key/value pair
Name string `json:"name"`
// Value is the value for an associated key in a key/value pair
Value string `json:"value"`
}
// MessageCardSectionImage represents an image as used by the heroImage and
// images properties of a section.
type MessageCardSectionImage struct {
// Image is the URL to the image.
Image string `json:"image"`
// Title is a short description of the image. Typically, this description
// is displayed in a tooltip as the user hovers their mouse over the
// image.
Title string `json:"title"`
}
// MessageCardSection represents a section to include in a message card.
type MessageCardSection struct {
// Title is the title property of a section. This property is displayed
// in a font that stands out, while not as prominent as the card's title.
// It is meant to introduce the section and summarize its content,
// similarly to how the card's title property is meant to summarize the
// whole card.
Title string `json:"title,omitempty"`
// Text is the section's text property. This property is very similar to
// the text property of the card. It can be used for the same purpose.
Text string `json:"text,omitempty"`
// ActivityImage is a property used to display a picture associated with
// the subject of a message card. For example, this might be the portrait
// of a person who performed an activity that the message card is
// associated with.
ActivityImage string `json:"activityImage,omitempty"`
// ActivityTitle is a property used to summarize the activity associated
// with a message card.
ActivityTitle string `json:"activityTitle,omitempty"`
// ActivitySubtitle is a property used to show brief, but extended
// information about an activity associated with a message card. Examples
// include the date and time the associated activity was taken or the
// handle of a person associated with the activity.
ActivitySubtitle string `json:"activitySubtitle,omitempty"`
// ActivityText is a property used to provide details about the activity.
// For example, if the message card is used to deliver updates about a
// topic, then this property would be used to hold the bulk of the content
// for the update notification.
ActivityText string `json:"activityText,omitempty"`
// Markdown represents a toggle to enable or disable Markdown formatting.
// By default, all text fields in a card and its sections can be formatted
// using basic Markdown.
Markdown bool `json:"markdown,omitempty"`
// StartGroup is the section's startGroup property. This property marks
// the start of a logical group of information. Typically, sections with
// startGroup set to true will be visually separated from previous card
// elements.
StartGroup bool `json:"startGroup,omitempty"`
// HeroImage is a property that allows for setting an image as the
// centerpiece of a message card. This property can also be used to add a
// banner to the message card.
// Note: heroImage is not currently supported by Microsoft Teams
// https://stackoverflow.com/a/45389789
// We use a pointer to this type in order to have the json package
// properly omit this field if not explicitly set.
// https://github.com/golang/go/issues/11939
// https://stackoverflow.com/questions/18088294/how-to-not-marshal-an-empty-struct-into-json-with-go
// https://stackoverflow.com/questions/33447334/golang-json-marshal-how-to-omit-empty-nested-struct
HeroImage *MessageCardSectionImage `json:"heroImage,omitempty"`
// Facts is a collection of MessageCardSectionFact values. A section entry
// usually is displayed in a two-column key/value format.
Facts []MessageCardSectionFact `json:"facts,omitempty"`
// Images is a property that allows for the inclusion of a photo gallery
// inside a section.
// We use a slice of pointers to this type in order to have the json
// package properly omit this field if not explicitly set.
// https://github.com/golang/go/issues/11939
// https://stackoverflow.com/questions/18088294/how-to-not-marshal-an-empty-struct-into-json-with-go
// https://stackoverflow.com/questions/33447334/golang-json-marshal-how-to-omit-empty-nested-struct
Images []*MessageCardSectionImage `json:"images,omitempty"`
}
// MessageCard represents a legacy actionable message card used via Office 365
// or Microsoft Teams connectors.
type MessageCard struct {
// Required; must be set to "MessageCard"
Type string `json:"@type"`
// Required; must be set to "https://schema.org/extensions"
Context string `json:"@context"`
// Summary is required if the card does not contain a text property,
// otherwise optional. The summary property is typically displayed in the
// list view in Outlook, as a way to quickly determine what the card is
// all about. Summary appears to only be used when there are sections defined
Summary string `json:"summary,omitempty"`
// Title is the title property of a card. is meant to be rendered in a
// prominent way, at the very top of the card. Use it to introduce the
// content of the card in such a way users will immediately know what to
// expect.
Title string `json:"title,omitempty"`
// Text is required if the card does not contain a summary property,
// otherwise optional. The text property is meant to be displayed in a
// normal font below the card's title. Use it to display content, such as
// the description of the entity being referenced, or an abstract of a
// news article.
Text string `json:"text,omitempty"`
// Specifies a custom brand color for the card. The color will be
// displayed in a non-obtrusive manner.
ThemeColor string `json:"themeColor,omitempty"`
// Sections is a collection of sections to include in the card.
Sections []*MessageCardSection `json:"sections,omitempty"`
}
// AddSection adds one or many additional MessageCardSection values to a
// MessageCard. Validation is performed to reject invalid values with an error
// message.
func (mc *MessageCard) AddSection(section ...*MessageCardSection) error {
for _, s := range section {
// bail if a completely nil section provided
if s == nil {
return fmt.Errorf("func AddSection: nil MessageCardSection received")
}
// Perform validation of all MessageCardSection fields in an effort to
// avoid adding a MessageCardSection with zero value fields. This is
// done to avoid generating an empty sections JSON array since the
// Sections slice for the MessageCard type would technically not be at
// a zero value state. Due to this non-zero value state, the
// encoding/json package would end up including the Sections struct
// field in the output JSON.
// See also https://github.com/golang/go/issues/11939
switch {
// If any of these cases trigger, skip over the `default` case
// statement and add the section.
case s.Images != nil:
case s.Facts != nil:
case s.HeroImage != nil:
case s.StartGroup:
case s.Markdown:
case s.ActivityText != "":
case s.ActivitySubtitle != "":
case s.ActivityTitle != "":
case s.ActivityImage != "":
case s.Text != "":
case s.Title != "":
default:
return fmt.Errorf("all fields found to be at zero-value, skipping section")
}
mc.Sections = append(mc.Sections, s)
}
return nil
}
// AddFact adds one or many additional MessageCardSectionFact values to a
// MessageCardSection
func (mcs *MessageCardSection) AddFact(fact ...MessageCardSectionFact) error {
for _, f := range fact {
if f.Name == "" {
return fmt.Errorf("empty Name field received for new fact: %+v", f)
}
if f.Value == "" {
return fmt.Errorf("empty Name field received for new fact: %+v", f)
}
}
mcs.Facts = append(mcs.Facts, fact...)
return nil
}
// AddFactFromKeyValue accepts a key and slice of values and converts them to
// MessageCardSectionFact values
func (mcs *MessageCardSection) AddFactFromKeyValue(key string, values ...string) error {
// validate arguments
if key == "" {
return errors.New("empty key received for new fact")
}
if len(values) < 1 {
return errors.New("no values received for new fact")
}
fact := MessageCardSectionFact{
Name: key,
Value: strings.Join(values, ", "),
}
// TODO: Explicitly define or use constructor?
// fact := NewMessageCardSectionFact()
// fact.Name = key
// fact.Value = strings.Join(values, ", ")
mcs.Facts = append(mcs.Facts, fact)
// if we made it this far then all should be well
return nil
}
// AddImage adds an image to a MessageCard section. These images are used to
// provide a photo gallery inside a MessageCard section.
func (mcs *MessageCardSection) AddImage(sectionImage ...MessageCardSectionImage) error {
for i := range sectionImage {
if sectionImage[i].Image == "" {
return fmt.Errorf("cannot add empty image URL")
}
if sectionImage[i].Title == "" {
return fmt.Errorf("cannot add empty image title")
}
mcs.Images = append(mcs.Images, §ionImage[i])
}
return nil
}
// AddHeroImageStr adds a Hero Image to a MessageCard section using string
// arguments. This image is used as the centerpiece or banner of a message
// card.
func (mcs *MessageCardSection) AddHeroImageStr(imageURL string, imageTitle string) error {
if imageURL == "" {
return fmt.Errorf("cannot add empty hero image URL")
}
if imageTitle == "" {
return fmt.Errorf("cannot add empty hero image title")
}
heroImage := MessageCardSectionImage{
Image: imageURL,
Title: imageTitle,
}
// TODO: Explicitly define or use constructor?
// heroImage := NewMessageCardSectionImage()
// heroImage.Image = imageURL
// heroImage.Title = imageTitle
mcs.HeroImage = &heroImage
// our validation checks didn't find any problems
return nil
}
// AddHeroImage adds a Hero Image to a MessageCard section using a
// MessageCardSectionImage argument. This image is used as the centerpiece or
// banner of a message card.
func (mcs *MessageCardSection) AddHeroImage(heroImage MessageCardSectionImage) error {
if heroImage.Image == "" {
return fmt.Errorf("cannot add empty hero image URL")
}
if heroImage.Title == "" {
return fmt.Errorf("cannot add empty hero image title")
}
mcs.HeroImage = &heroImage
// our validation checks didn't find any problems
return nil
}
// NewMessageCard creates a new message card with fields required by the
// legacy message card format already predefined
func NewMessageCard() MessageCard {
// define expected values to meet Office 365 Connector card requirements
// https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#card-fields
msgCard := MessageCard{
Type: "MessageCard",
Context: "https://schema.org/extensions",
}
return msgCard
}
// NewMessageCardSection creates an empty message card section
func NewMessageCardSection() *MessageCardSection {
msgCardSection := MessageCardSection{}
return &msgCardSection
}
// NewMessageCardSectionFact creates an empty message card section fact
func NewMessageCardSectionFact() MessageCardSectionFact {
msgCardSectionFact := MessageCardSectionFact{}
return msgCardSectionFact
}
// NewMessageCardSectionImage creates an empty image for use with message card
// section
func NewMessageCardSectionImage() MessageCardSectionImage {
msgCardSectionImage := MessageCardSectionImage{}
return msgCardSectionImage
}