Skip to content

Commit 3353adb

Browse files
authored
Unmarshal messages correctly for all subtypes (#1408)
When we get a 'message' event, the message details (blocks, attachments, etc.) are all included on the top level struct. When it's a 'message_changed' event, these details are instead nested under a `message` key. This is really really confusing! We can't just nest a `Msg` struct inside our MessageEvent, because the keys of other message events conflict with the `Msg` keys (e.g. both a `message` and a `message_changed` event will have a `user` key). To avoid this, we implement a custom unmarshaller which will: 1. If there's a `message` key in the payload, unmarshal it into the Message field 2. If there's no `message` key, populate the Message field with the event itself, assuming that it's an event with no subtype and so the message info is at the top level. This means some information is represented twice in the same struct, but that is less bad than it being unclear to a caller where they should go to access e.g. the message text. Also reworks the tests so they use examples pulled directly from Slack's documentation.
2 parents cf272ff + 462a182 commit 3353adb

File tree

2 files changed

+159
-41
lines changed

2 files changed

+159
-41
lines changed

slackevents/inner_events.go

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
package slackevents
44

55
import (
6+
"encoding/json"
7+
68
"github.com/slack-go/slack"
79
)
810

@@ -283,9 +285,16 @@ type MessageEvent struct {
283285
UserTeam string `json:"user_team,omitempty"`
284286
SourceTeam string `json:"source_team,omitempty"`
285287

288+
// When we get a 'message' event with no subtype, i.e. telling us about a new
289+
// message, the message information is stored at the top level. But when we get
290+
// a 'message_changed' event, the message information is stored in
291+
// the Message property. This is really hard to represent nicely in Go, so we use
292+
// a custom JSON unmarshaller to populate the Message field in both cases.
293+
Message *slack.Msg `json:"message,omitempty"`
294+
// Root is set if the SubType is `thread_broadcast`.
295+
Root *slack.Msg `json:"root,omitempty"`
286296
// Edited Message
287-
Message *slack.Message `json:"message,omitempty"`
288-
PreviousMessage *slack.Message `json:"previous_message,omitempty"`
297+
PreviousMessage *slack.Msg `json:"previous_message,omitempty"`
289298

290299
// Deleted Message
291300
DeletedTimeStamp string `json:"deleted_ts,omitempty"`
@@ -299,6 +308,40 @@ type MessageEvent struct {
299308
Icons *Icon `json:"icons,omitempty"`
300309
}
301310

311+
// UnmarshalJSON implements the json.Unmarshaler interface for MessageEvent.
312+
// This custom unmarshaler handles both regular messages and message_changed events
313+
// by normalizing the message data into the Message field.
314+
func (e *MessageEvent) UnmarshalJSON(data []byte) error {
315+
// First, unmarshal into an anonymous struct to avoid infinite recursion
316+
// when calling json.Unmarshal on the MessageEvent type itself
317+
type MessageEventAlias MessageEvent
318+
alias := struct {
319+
MessageEventAlias
320+
}{}
321+
322+
if err := json.Unmarshal(data, &alias.MessageEventAlias); err != nil {
323+
return err
324+
}
325+
326+
// Copy all fields from alias to the original struct
327+
*e = MessageEvent(alias.MessageEventAlias)
328+
329+
// Now check if there's no Message field (which would happen for regular messages)
330+
if e.Message == nil {
331+
// For regular messages, the message content is at the top level,
332+
// so we need to unmarshal the data again into a slack.Msg
333+
msg := &slack.Msg{}
334+
if err := json.Unmarshal(data, msg); err != nil {
335+
return err
336+
}
337+
338+
// Set the Message field to the unmarshaled msg
339+
e.Message = msg
340+
}
341+
342+
return nil
343+
}
344+
302345
// MemberJoinedChannelEvent A member joined a public or private channel
303346
type MemberJoinedChannelEvent struct {
304347
Type string `json:"type"`

slackevents/inner_events_test.go

Lines changed: 114 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -346,28 +346,71 @@ func TestMessageEvent(t *testing.T) {
346346
"channel_type": "channel",
347347
"source_team": "T3MQV36V7",
348348
"user_team": "T3MQV36V7",
349-
"message": {
350-
"text": "To infinity and beyond.",
351-
"edited": {
352-
"user": "U2147483697",
353-
"ts": "1355517524.000000"
354-
}
355-
},
356349
"metadata": {
357350
"event_type": "example",
358351
"event_payload": {
359352
"key": "value"
360353
}
361-
},
362-
"previous_message": {
363-
"text": "Live long and prospect."
364354
}
365355
}
366356
`)
367-
err := json.Unmarshal(rawE, &MessageEvent{})
357+
var e MessageEvent
358+
err := json.Unmarshal(rawE, &e)
368359
if err != nil {
369360
t.Error(err)
370361
}
362+
363+
if e.Channel != "G024BE91L" {
364+
t.Error(fmt.Errorf("expected channel G024BE91L, got %s", e.Channel))
365+
}
366+
if e.User != "U2147483697" {
367+
t.Error(fmt.Errorf("expected user U2147483697, got %s", e.User))
368+
}
369+
if e.Text != "Live long and prospect." {
370+
t.Error(fmt.Errorf("expected e.Text Live long and prospect., got %s", e.Text))
371+
}
372+
if e.Message.Text != "Live long and prospect." {
373+
t.Error(fmt.Errorf("expected e.Message.Text Live long and prospect., got %s", e.Message.Text))
374+
}
375+
376+
}
377+
378+
func TestMessageChangedEvent(t *testing.T) {
379+
rawE := []byte(`
380+
{
381+
"type": "message",
382+
"subtype": "message_changed",
383+
"hidden": true,
384+
"channel": "G024BE91L",
385+
"ts": "1358878755.000001",
386+
"message": {
387+
"type": "message",
388+
"user": "U123ABC456",
389+
"text": "Live long and prospect.",
390+
"ts": "1355517523.000005",
391+
"edited": {
392+
"user": "U123ABC456",
393+
"ts": "1358878755.000001"
394+
}
395+
}
396+
}
397+
`)
398+
399+
var e MessageEvent
400+
err := json.Unmarshal(rawE, &e)
401+
if err != nil {
402+
t.Error(err)
403+
}
404+
405+
if e.Channel != "G024BE91L" {
406+
t.Error(fmt.Errorf("expected channel G024BE91L, got %s", e.Channel))
407+
}
408+
if e.Message.Text != "Live long and prospect." {
409+
t.Error(fmt.Errorf("expected e.Message.Text Live long and prospect., got %s", e.Message.Text))
410+
}
411+
if e.Message.Edited.User != "U123ABC456" {
412+
t.Error(fmt.Errorf("expected e.Message.Edited.User U123ABC456, got %s", e.Message.Edited.User))
413+
}
371414
}
372415

373416
func TestBotMessageEvent(t *testing.T) {
@@ -393,42 +436,74 @@ func TestThreadBroadcastEvent(t *testing.T) {
393436
{
394437
"type": "message",
395438
"subtype": "thread_broadcast",
396-
"channel": "G024BE91L",
397-
"user": "U2147483697",
398-
"text": "Live long and prospect.",
399-
"ts": "1355517523.000005",
400-
"event_ts": "1355517523.000005",
401-
"channel_type": "channel",
402-
"source_team": "T3MQV36V7",
403-
"user_team": "T3MQV36V7",
404-
"message": {
405-
"text": "To infinity and beyond.",
406-
"root": {
407-
"text": "To infinity and beyond.",
408-
"ts": "1355517523.000005"
409-
},
410-
"edited": {
411-
"user": "U2147483697",
412-
"ts": "1355517524.000000"
413-
}
439+
"text": "broadcasting this reply",
440+
"user": "U123ABC456",
441+
"ts": "1673464745.620769",
442+
"thread_ts": "1673464730.703009",
443+
"root": {
444+
"client_msg_id": "123abc456-...",
445+
"type": "message",
446+
"text": "This is the original message",
447+
"user": "U123ABC456",
448+
"ts": "1673464730.703009",
449+
"blocks": [
450+
{
451+
"type": "rich_text",
452+
"block_id": "qTg",
453+
"elements": [
454+
{
455+
"type": "rich_text_section",
456+
"elements": [
457+
{
458+
"type": "text",
459+
"text": "This is the original message"
460+
}
461+
]
462+
}
463+
]
464+
}
465+
],
466+
"team": "T123ABC456",
467+
"thread_ts": "1673464730.703009",
468+
"reply_count": 1,
469+
"reply_users_count": 1,
470+
"latest_reply": "1673464745.620769",
471+
"reply_users": [
472+
"U123ABC456"
473+
],
474+
"is_locked": false
414475
},
415-
"previous_message": {
416-
"text": "Live long and prospect."
417-
}
418-
}
476+
"blocks": [
477+
{
478+
"type": "rich_text",
479+
"block_id": "BVp",
480+
"elements": [
481+
{
482+
"type": "rich_text_section",
483+
"elements": [
484+
{
485+
"type": "text",
486+
"text": "broadcasting this reply"
487+
}
488+
]
489+
}
490+
]
491+
}
492+
],
493+
"client_msg_id": "123abc456-...",
494+
"channel": "C123ABC456",
495+
"event_ts": "1673464745.620769",
496+
"channel_type": "channel"
497+
}
419498
`)
420499

421500
var me MessageEvent
422501
if err := json.Unmarshal(rawE, &me); err != nil {
423502
t.Error(err)
424503
}
425504

426-
if me.Message.Root == nil {
427-
t.Fatal("me.Message.Root is nil")
428-
}
429-
430-
if me.Message.Root.Timestamp != "1355517523.000005" {
431-
t.Errorf("me.Message.Root.Timestamp = %q, want %q", me.Message.Root.Timestamp, "1355517523.000005")
505+
if me.Root.Timestamp != "1673464730.703009" {
506+
t.Errorf("me.Root.Timestamp = %q, want %q", me.Root.Timestamp, "1673464730.703009")
432507
}
433508
}
434509

0 commit comments

Comments
 (0)