summaryrefslogtreecommitdiffstats
path: root/vendor/maunium.net/go/mautrix/event/events.go
blob: d2b61046bb271f2aafb647a5c01fc5ed90f51ac1 (plain) (blame)
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
// Copyright (c) 2020 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package event

import (
	"encoding/json"
	"time"

	"maunium.net/go/mautrix/id"
)

// Event represents a single Matrix event.
type Event struct {
	StateKey  *string    `json:"state_key,omitempty"`        // The state key for the event. Only present on State Events.
	Sender    id.UserID  `json:"sender,omitempty"`           // The user ID of the sender of the event
	Type      Type       `json:"type"`                       // The event type
	Timestamp int64      `json:"origin_server_ts,omitempty"` // The unix timestamp when this message was sent by the origin server
	ID        id.EventID `json:"event_id,omitempty"`         // The unique ID of this event
	RoomID    id.RoomID  `json:"room_id,omitempty"`          // The room the event was sent to. May be nil (e.g. for presence)
	Content   Content    `json:"content"`                    // The JSON content of the event.
	Redacts   id.EventID `json:"redacts,omitempty"`          // The event ID that was redacted if a m.room.redaction event
	Unsigned  Unsigned   `json:"unsigned,omitempty"`         // Unsigned content set by own homeserver.

	Mautrix MautrixInfo `json:"-"`

	ToUserID   id.UserID   `json:"to_user_id,omitempty"`   // The user ID that the to-device event was sent to. Only present in MSC2409 appservice transactions.
	ToDeviceID id.DeviceID `json:"to_device_id,omitempty"` // The device ID that the to-device event was sent to. Only present in MSC2409 appservice transactions.
}

type eventForMarshaling struct {
	StateKey  *string    `json:"state_key,omitempty"`
	Sender    id.UserID  `json:"sender,omitempty"`
	Type      Type       `json:"type"`
	Timestamp int64      `json:"origin_server_ts,omitempty"`
	ID        id.EventID `json:"event_id,omitempty"`
	RoomID    id.RoomID  `json:"room_id,omitempty"`
	Content   Content    `json:"content"`
	Redacts   id.EventID `json:"redacts,omitempty"`
	Unsigned  *Unsigned  `json:"unsigned,omitempty"`

	PrevContent   *Content    `json:"prev_content,omitempty"`
	ReplacesState *id.EventID `json:"replaces_state,omitempty"`

	ToUserID   id.UserID   `json:"to_user_id,omitempty"`
	ToDeviceID id.DeviceID `json:"to_device_id,omitempty"`
}

// UnmarshalJSON unmarshals the event, including moving prev_content from the top level to inside unsigned.
func (evt *Event) UnmarshalJSON(data []byte) error {
	var efm eventForMarshaling
	err := json.Unmarshal(data, &efm)
	if err != nil {
		return err
	}
	evt.StateKey = efm.StateKey
	evt.Sender = efm.Sender
	evt.Type = efm.Type
	evt.Timestamp = efm.Timestamp
	evt.ID = efm.ID
	evt.RoomID = efm.RoomID
	evt.Content = efm.Content
	evt.Redacts = efm.Redacts
	if efm.Unsigned != nil {
		evt.Unsigned = *efm.Unsigned
	}
	if efm.PrevContent != nil && evt.Unsigned.PrevContent == nil {
		evt.Unsigned.PrevContent = efm.PrevContent
	}
	if efm.ReplacesState != nil && *efm.ReplacesState != "" && evt.Unsigned.ReplacesState == "" {
		evt.Unsigned.ReplacesState = *efm.ReplacesState
	}
	evt.ToUserID = efm.ToUserID
	evt.ToDeviceID = efm.ToDeviceID
	return nil
}

// MarshalJSON marshals the event, including omitting the unsigned field if it's empty.
//
// This is necessary because Unsigned is not a pointer (for convenience reasons),
// and encoding/json doesn't know how to check if a non-pointer struct is empty.
//
// TODO(tulir): maybe it makes more sense to make Unsigned a pointer and make an easy and safe way to access it?
func (evt *Event) MarshalJSON() ([]byte, error) {
	unsigned := &evt.Unsigned
	if unsigned.IsEmpty() {
		unsigned = nil
	}
	return json.Marshal(&eventForMarshaling{
		StateKey:   evt.StateKey,
		Sender:     evt.Sender,
		Type:       evt.Type,
		Timestamp:  evt.Timestamp,
		ID:         evt.ID,
		RoomID:     evt.RoomID,
		Content:    evt.Content,
		Redacts:    evt.Redacts,
		Unsigned:   unsigned,
		ToUserID:   evt.ToUserID,
		ToDeviceID: evt.ToDeviceID,
	})
}

type MautrixInfo struct {
	TrustState    id.TrustState
	ForwardedKeys bool
	WasEncrypted  bool
	TrustSource   *id.Device

	ReceivedAt         time.Time
	DecryptionDuration time.Duration

	CheckpointSent bool
}

func (evt *Event) GetStateKey() string {
	if evt.StateKey != nil {
		return *evt.StateKey
	}
	return ""
}

type StrippedState struct {
	Content  Content   `json:"content"`
	Type     Type      `json:"type"`
	StateKey string    `json:"state_key"`
	Sender   id.UserID `json:"sender"`
}

type Unsigned struct {
	PrevContent     *Content        `json:"prev_content,omitempty"`
	PrevSender      id.UserID       `json:"prev_sender,omitempty"`
	ReplacesState   id.EventID      `json:"replaces_state,omitempty"`
	Age             int64           `json:"age,omitempty"`
	TransactionID   string          `json:"transaction_id,omitempty"`
	Relations       *Relations      `json:"m.relations,omitempty"`
	RedactedBecause *Event          `json:"redacted_because,omitempty"`
	InviteRoomState []StrippedState `json:"invite_room_state,omitempty"`

	BeeperHSOrder int64 `json:"com.beeper.hs.order,omitempty"`
}

func (us *Unsigned) IsEmpty() bool {
	return us.PrevContent == nil && us.PrevSender == "" && us.ReplacesState == "" && us.Age == 0 &&
		us.TransactionID == "" && us.RedactedBecause == nil && us.InviteRoomState == nil && us.Relations == nil
}