package main import "encoding/json" // Event is a Matrix ClientEvent as delivered in an appservice transaction. Each // event carries its own room_id (unlike /sync, where it's implied by the room // bucket). Content stays raw so each handler decodes only the shape it needs. type Event struct { Type string `json:"type"` RoomID string `json:"room_id"` Sender string `json:"sender"` EventID string `json:"event_id"` OriginServerTS int64 `json:"origin_server_ts"` StateKey *string `json:"state_key,omitempty"` Content json.RawMessage `json:"content"` } // Mentions is the MSC3952 intentional-mentions block. It can legitimately be the // empty object `{}` (cinny writes it on every send), so UserIDs may be nil — // callers must safe-deref (F29). type Mentions struct { UserIDs []string `json:"user_ids"` Room bool `json:"room"` } type InReplyTo struct { EventID string `json:"event_id"` } type RelatesTo struct { RelType string `json:"rel_type"` EventID string `json:"event_id"` InReplyTo *InReplyTo `json:"m.in_reply_to"` } // MessageContent is the decoded m.room.message content we care about. type MessageContent struct { MsgType string `json:"msgtype"` Body string `json:"body"` Format string `json:"format"` FormattedBody string `json:"formatted_body"` Mentions *Mentions `json:"m.mentions"` RelatesTo *RelatesTo `json:"m.relates_to"` NewContent json.RawMessage `json:"m.new_content"` } func (e *Event) DecodeMessage() (*MessageContent, bool) { if e.Type != "m.room.message" { return nil, false } var mc MessageContent if err := json.Unmarshal(e.Content, &mc); err != nil { return nil, false } return &mc, true } // IsReplace reports whether the message is an `m.replace` edit. Edits re-carry // m.mentions and must NOT re-trigger or double-bill (F16). func (mc *MessageContent) IsReplace() bool { return mc.RelatesTo != nil && mc.RelatesTo.RelType == "m.replace" } // membershipOf extracts the membership from an m.room.member event content. func (e *Event) membershipOf() string { var m struct { Membership string `json:"membership"` } if json.Unmarshal(e.Content, &m) != nil { return "" } return m.Membership }