aboutsummaryrefslogtreecommitdiff
path: root/vendor/maunium.net/go/gomatrix
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/maunium.net/go/gomatrix')
-rw-r--r--vendor/maunium.net/go/gomatrix/client.go145
-rw-r--r--vendor/maunium.net/go/gomatrix/events.go455
-rw-r--r--vendor/maunium.net/go/gomatrix/reply.go96
-rw-r--r--vendor/maunium.net/go/gomatrix/requests.go8
-rw-r--r--vendor/maunium.net/go/gomatrix/responses.go2
-rw-r--r--vendor/maunium.net/go/gomatrix/room.go20
-rw-r--r--vendor/maunium.net/go/gomatrix/sync.go15
7 files changed, 612 insertions, 129 deletions
diff --git a/vendor/maunium.net/go/gomatrix/client.go b/vendor/maunium.net/go/gomatrix/client.go
index 7725ac3..0806138 100644
--- a/vendor/maunium.net/go/gomatrix/client.go
+++ b/vendor/maunium.net/go/gomatrix/client.go
@@ -6,13 +6,16 @@ package gomatrix
import (
"bytes"
"encoding/json"
+ "errors"
"fmt"
"io"
"io/ioutil"
+ "maunium.net/go/maulogger"
"net/http"
"net/url"
"path"
"strconv"
+ "strings"
"sync"
"time"
)
@@ -26,6 +29,7 @@ type Client struct {
Client *http.Client // The underlying HTTP client which will be used to make HTTP requests.
Syncer Syncer // The thing which can process /sync responses
Store Storer // The thing which can store rooms/tokens/ids
+ Logger maulogger.Logger
// The ?user_id= query parameter for application services. This must be set *prior* to calling a method. If this is empty,
// no user_id parameter will be sent.
@@ -39,6 +43,7 @@ type Client struct {
// HTTPError An HTTP Error response, which may wrap an underlying native Go Error.
type HTTPError struct {
WrappedError error
+ RespError *RespError
Message string
Code int
}
@@ -177,6 +182,14 @@ func (cli *Client) StopSync() {
cli.incrementSyncingID()
}
+func (cli *Client) LogRequest(req *http.Request, body string) {
+ if cli.Logger == nil {
+ return
+ }
+
+ cli.Logger.Debugfln("%s %s %s", req.Method, req.URL.Path, body)
+}
+
// MakeRequest makes a JSON HTTP request to the given URL.
// If "resBody" is not nil, the response body will be json.Unmarshalled into it.
//
@@ -186,12 +199,14 @@ func (cli *Client) StopSync() {
func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) ([]byte, error) {
var req *http.Request
var err error
+ logBody := "{}"
if reqBody != nil {
var jsonStr []byte
jsonStr, err = json.Marshal(reqBody)
if err != nil {
return nil, err
}
+ logBody = string(jsonStr)
req, err = http.NewRequest(method, httpURL, bytes.NewBuffer(jsonStr))
} else {
req, err = http.NewRequest(method, httpURL, nil)
@@ -201,6 +216,7 @@ func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{
return nil, err
}
req.Header.Set("Content-Type", "application/json")
+ cli.LogRequest(req, logBody)
res, err := cli.Client.Do(req)
if res != nil {
defer res.Body.Close()
@@ -211,9 +227,11 @@ func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{
contents, err := ioutil.ReadAll(res.Body)
if res.StatusCode/100 != 2 { // not 2xx
var wrap error
- var respErr RespError
- if _ = json.Unmarshal(contents, &respErr); respErr.ErrCode != "" {
+ respErr := &RespError{}
+ if _ = json.Unmarshal(contents, respErr); respErr.ErrCode != "" {
wrap = respErr
+ } else {
+ respErr = nil
}
// If we failed to decode as RespError, don't just drop the HTTP body, include it in the
@@ -227,6 +245,7 @@ func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{
Code: res.StatusCode,
Message: msg,
WrappedError: wrap,
+ RespError: respErr,
}
}
if err != nil {
@@ -342,7 +361,7 @@ func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) {
}
}
if res == nil {
- return nil, fmt.Errorf("registration failed: does this server support m.login.dummy?")
+ return nil, fmt.Errorf("registration failed: does this server support m.login.dummy? ")
}
return res, nil
}
@@ -442,17 +461,38 @@ func (cli *Client) SetAvatarURL(url string) (err error) {
// SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
-func (cli *Client) SendMessageEvent(roomID string, eventType string, contentJSON interface{}) (resp *RespSendEvent, err error) {
+func (cli *Client) SendMessageEvent(roomID string, eventType EventType, contentJSON interface{}) (resp *RespSendEvent, err error) {
+ txnID := txnID()
+ urlPath := cli.BuildURL("rooms", roomID, "send", eventType.String(), txnID)
+ _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
+ return
+}
+
+// SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
+// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
+func (cli *Client) SendMassagedMessageEvent(roomID string, eventType EventType, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) {
txnID := txnID()
- urlPath := cli.BuildURL("rooms", roomID, "send", eventType, txnID)
+ urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "send", eventType.String(), txnID}, map[string]string{
+ "ts": strconv.FormatInt(ts, 10),
+ })
_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
return
}
// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey
// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
-func (cli *Client) SendStateEvent(roomID, eventType, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) {
- urlPath := cli.BuildURL("rooms", roomID, "state", eventType, stateKey)
+func (cli *Client) SendStateEvent(roomID string, eventType EventType, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) {
+ urlPath := cli.BuildURL("rooms", roomID, "state", eventType.String(), stateKey)
+ _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
+ return
+}
+
+// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey
+// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
+func (cli *Client) SendMassagedStateEvent(roomID string, eventType EventType, stateKey string, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) {
+ urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "state", eventType.String(), stateKey}, map[string]string{
+ "ts": strconv.FormatInt(ts, 10),
+ })
_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
return
}
@@ -460,37 +500,39 @@ func (cli *Client) SendStateEvent(roomID, eventType, stateKey string, contentJSO
// SendText sends an m.room.message event into the given room with a msgtype of m.text
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-text
func (cli *Client) SendText(roomID, text string) (*RespSendEvent, error) {
- return cli.SendMessageEvent(roomID, "m.room.message",
- TextMessage{"m.text", text})
+ return cli.SendMessageEvent(roomID, EventMessage, Content{
+ MsgType: MsgText,
+ Body: text,
+ })
}
// SendImage sends an m.room.message event into the given room with a msgtype of m.image
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-image
func (cli *Client) SendImage(roomID, body, url string) (*RespSendEvent, error) {
- return cli.SendMessageEvent(roomID, "m.room.message",
- ImageMessage{
- MsgType: "m.image",
- Body: body,
- URL: url,
- })
+ return cli.SendMessageEvent(roomID, EventMessage, Content{
+ MsgType: MsgImage,
+ Body: body,
+ URL: url,
+ })
}
// SendVideo sends an m.room.message event into the given room with a msgtype of m.video
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
func (cli *Client) SendVideo(roomID, body, url string) (*RespSendEvent, error) {
- return cli.SendMessageEvent(roomID, "m.room.message",
- VideoMessage{
- MsgType: "m.video",
- Body: body,
- URL: url,
- })
+ return cli.SendMessageEvent(roomID, EventMessage, Content{
+ MsgType: MsgVideo,
+ Body: body,
+ URL: url,
+ })
}
// SendNotice sends an m.room.message event into the given room with a msgtype of m.notice
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-notice
func (cli *Client) SendNotice(roomID, text string) (*RespSendEvent, error) {
- return cli.SendMessageEvent(roomID, "m.room.message",
- TextMessage{"m.notice", text})
+ return cli.SendMessageEvent(roomID, EventMessage, Content{
+ MsgType: MsgNotice,
+ Body: text,
+ })
}
// RedactEvent redacts the given event. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
@@ -569,11 +611,18 @@ func (cli *Client) UserTyping(roomID string, typing bool, timeout int64) (resp *
return
}
+func (cli *Client) SetPresence(status string) (err error) {
+ req := ReqPresence{Presence: status}
+ u := cli.BuildURL("presence", cli.UserID, "status")
+ _, err = cli.MakeRequest("PUT", u, req, nil)
+ return
+}
+
// StateEvent gets a single state event in a room. It will attempt to JSON unmarshal into the given "outContent" struct with
// the HTTP response body, or return an error.
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey
-func (cli *Client) StateEvent(roomID, eventType, stateKey string, outContent interface{}) (err error) {
- u := cli.BuildURL("rooms", roomID, "state", eventType, stateKey)
+func (cli *Client) StateEvent(roomID string, eventType EventType, stateKey string, outContent interface{}) (err error) {
+ u := cli.BuildURL("rooms", roomID, "state", eventType.String(), stateKey)
_, err = cli.MakeRequest("GET", u, nil, outContent)
return
}
@@ -587,18 +636,48 @@ func (cli *Client) UploadLink(link string) (*RespMediaUpload, error) {
if err != nil {
return nil, err
}
- return cli.UploadToContentRepo(res.Body, res.Header.Get("Content-Type"), res.ContentLength)
+ return cli.Upload(res.Body, res.Header.Get("Content-Type"), res.ContentLength)
+}
+
+func (cli *Client) Download(mxcURL string) (io.ReadCloser, error) {
+ if !strings.HasPrefix(mxcURL, "mxc://") {
+ return nil, errors.New("invalid Matrix content URL")
+ }
+ parts := strings.Split(mxcURL[len("mxc://"):], "/")
+ if len(parts) != 2 {
+ return nil, errors.New("invalid Matrix content URL")
+ }
+ u := cli.BuildBaseURL("_matrix/media/r0/download", parts[0], parts[1])
+ resp, err := cli.Client.Get(u)
+ if err != nil {
+ return nil, err
+ }
+ return resp.Body, nil
+}
+
+func (cli *Client) DownloadBytes(mxcURL string) ([]byte, error) {
+ resp, err := cli.Download(mxcURL)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Close()
+ return ioutil.ReadAll(resp)
+}
+
+func (cli *Client) UploadBytes(data []byte, contentType string) (*RespMediaUpload, error) {
+ return cli.Upload(bytes.NewReader(data), contentType, int64(len(data)))
}
// UploadToContentRepo uploads the given bytes to the content repository and returns an MXC URI.
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload
-func (cli *Client) UploadToContentRepo(content io.Reader, contentType string, contentLength int64) (*RespMediaUpload, error) {
+func (cli *Client) Upload(content io.Reader, contentType string, contentLength int64) (*RespMediaUpload, error) {
req, err := http.NewRequest("POST", cli.BuildBaseURL("_matrix/media/r0/upload"), content)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", contentType)
req.ContentLength = contentLength
+ cli.LogRequest(req, fmt.Sprintf("%d bytes", contentLength))
res, err := cli.Client.Do(req)
if res != nil {
defer res.Body.Close()
@@ -666,6 +745,18 @@ func (cli *Client) Messages(roomID, from, to string, dir rune, limit int) (resp
return
}
+func (cli *Client) GetEvent(roomID, eventID string) (resp *Event, err error) {
+ urlPath := cli.BuildURL("rooms", roomID, "event", eventID)
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+func (cli *Client) MarkRead(roomID, eventID string) (err error) {
+ urlPath := cli.BuildURL("rooms", roomID, "receipt", "m.read", eventID)
+ _, err = cli.MakeRequest("POST", urlPath, struct{}{}, nil)
+ return
+}
+
// TurnServer returns turn server details and credentials for the client to use when initiating calls.
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-voip-turnserver
func (cli *Client) TurnServer() (resp *RespTurnServer, err error) {
diff --git a/vendor/maunium.net/go/gomatrix/events.go b/vendor/maunium.net/go/gomatrix/events.go
index 7233c7c..30166cd 100644
--- a/vendor/maunium.net/go/gomatrix/events.go
+++ b/vendor/maunium.net/go/gomatrix/events.go
@@ -1,110 +1,413 @@
package gomatrix
import (
- "html"
- "regexp"
+ "encoding/json"
+ "strings"
+ "sync"
+)
+
+type EventTypeClass int
+
+const (
+ // Normal message events
+ MessageEventType EventTypeClass = iota
+ // State events
+ StateEventType
+ // Ephemeral events
+ EphemeralEventType
+ // Account data events
+ AccountDataEventType
+ // Unknown events
+ UnknownEventType
+)
+
+type EventType struct {
+ Type string
+ Class EventTypeClass
+}
+
+func NewEventType(name string) EventType {
+ evtType := EventType{Type: name}
+ evtType.Class = evtType.GuessClass()
+ return evtType
+}
+
+func (et *EventType) IsState() bool {
+ return et.Class == StateEventType
+}
+
+func (et *EventType) IsEphemeral() bool {
+ return et.Class == EphemeralEventType
+}
+
+func (et *EventType) IsCustom() bool {
+ return !strings.HasPrefix(et.Type, "m.")
+}
+
+func (et *EventType) GuessClass() EventTypeClass {
+ switch et.Type {
+ case StateAliases.Type, StateCanonicalAlias.Type, StateCreate.Type, StateJoinRules.Type, StateMember.Type,
+ StatePowerLevels.Type, StateRoomName.Type, StateRoomAvatar.Type, StateTopic.Type, StatePinnedEvents.Type:
+ return StateEventType
+ case EphemeralEventReceipt.Type, EphemeralEventTyping.Type:
+ return EphemeralEventType
+ case AccountDataDirectChats.Type, AccountDataPushRules.Type, AccountDataRoomTags.Type:
+ return AccountDataEventType
+ case EventRedaction.Type, EventMessage.Type, EventSticker.Type:
+ return MessageEventType
+ default:
+ return UnknownEventType
+ }
+}
+
+func (et *EventType) UnmarshalJSON(data []byte) error {
+ err := json.Unmarshal(data, &et.Type)
+ if err != nil {
+ return err
+ }
+ et.Class = et.GuessClass()
+ return nil
+}
+
+func (et *EventType) MarshalJSON() ([]byte, error) {
+ return json.Marshal(&et.Type)
+}
+
+func (et *EventType) String() string {
+ return et.Type
+}
+
+// State events
+var (
+ StateAliases = EventType{"m.room.aliases", StateEventType}
+ StateCanonicalAlias = EventType{"m.room.canonical_alias", StateEventType}
+ StateCreate = EventType{"m.room.create", StateEventType}
+ StateJoinRules = EventType{"m.room.join_rules", StateEventType}
+ StateMember = EventType{"m.room.member", StateEventType}
+ StatePowerLevels = EventType{"m.room.power_levels", StateEventType}
+ StateRoomName = EventType{"m.room.name", StateEventType}
+ StateTopic = EventType{"m.room.topic", StateEventType}
+ StateRoomAvatar = EventType{"m.room.avatar", StateEventType}
+ StatePinnedEvents = EventType{"m.room.pinned_events", StateEventType}
+)
+
+// Message events
+var (
+ EventRedaction = EventType{"m.room.redaction", MessageEventType}
+ EventMessage = EventType{"m.room.message", MessageEventType}
+ EventSticker = EventType{"m.sticker", MessageEventType}
+)
+
+// Ephemeral events
+var (
+ EphemeralEventReceipt = EventType{"m.receipt", EphemeralEventType}
+ EphemeralEventTyping = EventType{"m.receipt", EphemeralEventType}
+)
+
+// Account data events
+var (
+ AccountDataDirectChats = EventType{"m.direct", AccountDataEventType}
+ AccountDataPushRules = EventType{"m.push_rules", AccountDataEventType}
+ AccountDataRoomTags = EventType{"m.tag", AccountDataEventType}
+)
+
+type MessageType string
+
+// Msgtypes
+const (
+ MsgText MessageType = "m.text"
+ MsgEmote = "m.emote"
+ MsgNotice = "m.notice"
+ MsgImage = "m.image"
+ MsgLocation = "m.location"
+ MsgVideo = "m.video"
+ MsgAudio = "m.audio"
+ MsgFile = "m.file"
+)
+
+type Format string
+
+// Message formats
+const (
+ FormatHTML Format = "org.matrix.custom.html"
)
// 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 string `json:"sender"` // The user ID of the sender of the event
- Type string `json:"type"` // The event type
- Timestamp int64 `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server
- ID string `json:"event_id"` // The unique ID of this event
- RoomID string `json:"room_id"` // The room the event was sent to. May be nil (e.g. for presence)
- Content map[string]interface{} `json:"content"` // The JSON content of the event.
- Redacts string `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.
+ StateKey *string `json:"state_key,omitempty"` // The state key for the event. Only present on State Events.
+ Sender string `json:"sender"` // The user ID of the sender of the event
+ Type EventType `json:"type"` // The event type
+ Timestamp int64 `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server
+ ID string `json:"event_id"` // The unique ID of this event
+ RoomID string `json:"room_id"` // 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 string `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.
+
+ InviteRoomState []StrippedState `json:"invite_room_state"`
+}
+
+func (evt *Event) GetStateKey() string {
+ if evt.StateKey != nil {
+ return *evt.StateKey
+ }
+ return ""
+}
+
+type StrippedState struct {
+ Content Content `json:"content"`
+ Type EventType `json:"type"`
+ StateKey string `json:"state_key"`
}
type Unsigned struct {
- PrevContent map[string]interface{} `json:"prev_content,omitempty"`
- PrevSender string `json:"prev_sender,omitempty"`
- ReplacesState string `json:"replaces_state,omitempty"`
- Age int64 `json:"age"`
+ PrevContent *Content `json:"prev_content,omitempty"`
+ PrevSender string `json:"prev_sender,omitempty"`
+ ReplacesState string `json:"replaces_state,omitempty"`
+ Age int64 `json:"age,omitempty"`
+}
+
+type Content struct {
+ VeryRaw json.RawMessage `json:"-"`
+ Raw map[string]interface{} `json:"-"`
+
+ MsgType MessageType `json:"msgtype,omitempty"`
+ Body string `json:"body,omitempty"`
+ Format Format `json:"format,omitempty"`
+ FormattedBody string `json:"formatted_body,omitempty"`
+
+ Info *FileInfo `json:"info,omitempty"`
+ URL string `json:"url,omitempty"`
+
+ // Membership key for easy access in m.room.member events
+ Membership Membership `json:"membership,omitempty"`
+
+ RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
+
+ PowerLevels
+ Member
+ Aliases []string `json:"aliases,omitempty"`
+ CanonicalAlias
+ RoomName
+ RoomTopic
+
+ RoomTags Tags `json:"tags,omitempty"`
+ TypingUserIDs []string `json:"user_ids,omitempty"`
}
-// Body returns the value of the "body" key in the event content if it is
-// present and is a string.
-func (event *Event) Body() (body string, ok bool) {
- value, exists := event.Content["body"]
- if !exists {
- return
+type serializableContent Content
+
+func (content *Content) UnmarshalJSON(data []byte) error {
+ content.VeryRaw = data
+ if err := json.Unmarshal(data, &content.Raw); err != nil {
+ return err
}
- body, ok = value.(string)
+ return json.Unmarshal(data, (*serializableContent)(content))
+}
+
+func (content *Content) UnmarshalPowerLevels() (pl PowerLevels, err error) {
+ err = json.Unmarshal(content.VeryRaw, &pl)
return
}
-// MessageType returns the value of the "msgtype" key in the event content if
-// it is present and is a string.
-func (event *Event) MessageType() (msgtype string, ok bool) {
- value, exists := event.Content["msgtype"]
- if !exists {
- return
- }
- msgtype, ok = value.(string)
+func (content *Content) UnmarshalMember() (m Member, err error) {
+ err = json.Unmarshal(content.VeryRaw, &m)
+ return
+}
+
+func (content *Content) UnmarshalCanonicalAlias() (ca CanonicalAlias, err error) {
+ err = json.Unmarshal(content.VeryRaw, &ca)
return
}
-// TextMessage is the contents of a Matrix formated message event.
-type TextMessage struct {
- MsgType string `json:"msgtype"`
- Body string `json:"body"`
+func (content *Content) GetInfo() *FileInfo {
+ if content.Info == nil {
+ content.Info = &FileInfo{}
+ }
+ return content.Info
}
-// ImageInfo contains info about an image - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-image
-type ImageInfo struct {
- Height uint `json:"h,omitempty"`
- Width uint `json:"w,omitempty"`
- Mimetype string `json:"mimetype,omitempty"`
- Size uint `json:"size,omitempty"`
+type Tags map[string]struct {
+ Order string `json:"order"`
}
-// VideoInfo contains info about a video - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
-type VideoInfo struct {
- Mimetype string `json:"mimetype,omitempty"`
- ThumbnailInfo ImageInfo `json:"thumbnail_info"`
- ThumbnailURL string `json:"thumbnail_url,omitempty"`
- Height uint `json:"h,omitempty"`
- Width uint `json:"w,omitempty"`
- Duration uint `json:"duration,omitempty"`
- Size uint `json:"size,omitempty"`
+type RoomName struct {
+ Name string `json:"name,omitempty"`
+}
+
+type RoomTopic struct {
+ Topic string `json:"topic,omitempty"`
+}
+
+// Membership is an enum specifying the membership state of a room member.
+type Membership string
+
+// The allowed membership states as specified in spec section 10.5.5.
+const (
+ MembershipJoin Membership = "join"
+ MembershipLeave Membership = "leave"
+ MembershipInvite Membership = "invite"
+ MembershipBan Membership = "ban"
+ MembershipKnock Membership = "knock"
+)
+
+type Member struct {
+ Membership Membership `json:"membership,omitempty"`
+ AvatarURL string `json:"avatar_url,omitempty"`
+ Displayname string `json:"displayname,omitempty"`
+ ThirdPartyInvite *ThirdPartyInvite `json:"third_party_invite,omitempty"`
+ Reason string `json:"reason,omitempty"`
+}
+
+type ThirdPartyInvite struct {
+ DisplayName string `json:"display_name"`
+ Signed struct {
+ Token string `json:"token"`
+ Signatures json.RawMessage `json:"signatures"`
+ MXID string `json:"mxid"`
+ }
+}
+
+type CanonicalAlias struct {
+ Alias string `json:"alias,omitempty"`
+}
+
+type PowerLevels struct {
+ usersLock sync.RWMutex `json:"-"`
+ Users map[string]int `json:"users,omitempty"`
+ UsersDefault int `json:"users_default,omitempty"`
+
+ eventsLock sync.RWMutex `json:"-"`
+ Events map[string]int `json:"events,omitempty"`
+ EventsDefault int `json:"events_default,omitempty"`
+
+ StateDefaultPtr *int `json:"state_default,omitempty"`
+
+ InvitePtr *int `json:"invite,omitempty"`
+ KickPtr *int `json:"kick,omitempty"`
+ BanPtr *int `json:"ban,omitempty"`
+ RedactPtr *int `json:"redact,omitempty"`
+}
+
+func (pl *PowerLevels) Invite() int {
+ if pl.InvitePtr != nil {
+ return *pl.InvitePtr
+ }
+ return 50
+}
+
+func (pl *PowerLevels) Kick() int {
+ if pl.KickPtr != nil {
+ return *pl.KickPtr
+ }
+ return 50
}
-// VideoMessage is an m.video - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
-type VideoMessage struct {
- MsgType string `json:"msgtype"`
- Body string `json:"body"`
- URL string `json:"url"`
- Info VideoInfo `json:"info"`
+func (pl *PowerLevels) Ban() int {
+ if pl.BanPtr != nil {
+ return *pl.BanPtr
+ }
+ return 50
+}
+
+func (pl *PowerLevels) Redact() int {
+ if pl.RedactPtr != nil {
+ return *pl.RedactPtr
+ }
+ return 50
+}
+
+func (pl *PowerLevels) StateDefault() int {
+ if pl.StateDefaultPtr != nil {
+ return *pl.StateDefaultPtr
+ }
+ return 50
+}
+
+func (pl *PowerLevels) GetUserLevel(userID string) int {
+ pl.usersLock.RLock()
+ defer pl.usersLock.RUnlock()
+ level, ok := pl.Users[userID]
+ if !ok {
+ return pl.UsersDefault
+ }
+ return level
+}
+
+func (pl *PowerLevels) SetUserLevel(userID string, level int) {
+ pl.usersLock.Lock()
+ defer pl.usersLock.Unlock()
+ if level == pl.UsersDefault {
+ delete(pl.Users, userID)
+ } else {
+ pl.Users[userID] = level
+ }
+}
+
+func (pl *PowerLevels) EnsureUserLevel(userID string, level int) bool {
+ existingLevel := pl.GetUserLevel(userID)
+ if existingLevel != level {
+ pl.SetUserLevel(userID, level)
+ return true
+ }
+ return false
}
-// ImageMessage is an m.image event
-type ImageMessage struct {
- MsgType string `json:"msgtype"`
- Body string `json:"body"`
- URL string `json:"url"`
- Info ImageInfo `json:"info"`
+func (pl *PowerLevels) GetEventLevel(eventType EventType) int {
+ pl.eventsLock.RLock()
+ defer pl.eventsLock.RUnlock()
+ level, ok := pl.Events[eventType.String()]
+ if !ok {
+ if eventType.IsState() {
+ return pl.StateDefault()
+ }
+ return pl.EventsDefault
+ }
+ return level
}
-// An HTMLMessage is the contents of a Matrix HTML formated message event.
-type HTMLMessage struct {
- Body string `json:"body"`
- MsgType string `json:"msgtype"`
- Format string `json:"format"`
- FormattedBody string `json:"formatted_body"`
+func (pl *PowerLevels) SetEventLevel(eventType EventType, level int) {
+ pl.eventsLock.Lock()
+ defer pl.eventsLock.Unlock()
+ if (eventType.IsState() && level == pl.StateDefault()) || (!eventType.IsState() && level == pl.EventsDefault) {
+ delete(pl.Events, eventType.String())
+ } else {
+ pl.Events[eventType.String()] = level
+ }
+}
+
+func (pl *PowerLevels) EnsureEventLevel(eventType EventType, level int) bool {
+ existingLevel := pl.GetEventLevel(eventType)
+ if existingLevel != level {
+ pl.SetEventLevel(eventType, level)
+ return true
+ }
+ return false
}
-var htmlRegex = regexp.MustCompile("<[^<]+?>")
+type FileInfo struct {
+ MimeType string `json:"mimetype,omitempty"`
+ ThumbnailInfo *FileInfo `json:"thumbnail_info,omitempty"`
+ ThumbnailURL string `json:"thumbnail_url,omitempty"`
+ Height int `json:"h,omitempty"`
+ Width int `json:"w,omitempty"`
+ Duration uint `json:"duration,omitempty"`
+ Size int `json:"size,omitempty"`
+}
-// GetHTMLMessage returns an HTMLMessage with the body set to a stripped version of the provided HTML, in addition
-// to the provided HTML.
-func GetHTMLMessage(msgtype, htmlText string) HTMLMessage {
- return HTMLMessage{
- Body: html.UnescapeString(htmlRegex.ReplaceAllLiteralString(htmlText, "")),
- MsgType: msgtype,
- Format: "org.matrix.custom.html",
- FormattedBody: htmlText,
+func (fileInfo *FileInfo) GetThumbnailInfo() *FileInfo {
+ if fileInfo.ThumbnailInfo == nil {
+ fileInfo.ThumbnailInfo = &FileInfo{}
}
+ return fileInfo.ThumbnailInfo
+}
+
+type RelatesTo struct {
+ InReplyTo InReplyTo `json:"m.in_reply_to,omitempty"`
+}
+
+type InReplyTo struct {
+ EventID string `json:"event_id,omitempty"`
+ // Not required, just for future-proofing
+ RoomID string `json:"room_id,omitempty"`
}
diff --git a/vendor/maunium.net/go/gomatrix/reply.go b/vendor/maunium.net/go/gomatrix/reply.go
new file mode 100644
index 0000000..6985421
--- /dev/null
+++ b/vendor/maunium.net/go/gomatrix/reply.go
@@ -0,0 +1,96 @@
+package gomatrix
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+
+ "golang.org/x/net/html"
+)
+
+var HTMLReplyFallbackRegex = regexp.MustCompile(`^<mx-reply>[\s\S]+?</mx-reply>`)
+
+func TrimReplyFallbackHTML(html string) string {
+ return HTMLReplyFallbackRegex.ReplaceAllString(html, "")
+}
+
+func TrimReplyFallbackText(text string) string {
+ if !strings.HasPrefix(text, "> ") || !strings.Contains(text, "\n") {
+ return text
+ }
+
+ lines := strings.Split(text, "\n")
+ for len(lines) > 0 && strings.HasPrefix(lines[0], "> ") {
+ lines = lines[1:]
+ }
+ return strings.TrimSpace(strings.Join(lines, "\n"))
+}
+
+func (content *Content) RemoveReplyFallback() {
+ if len(content.GetReplyTo()) > 0 {
+ if content.Format == FormatHTML {
+ content.FormattedBody = TrimReplyFallbackHTML(content.FormattedBody)
+ }
+ content.Body = TrimReplyFallbackText(content.Body)
+ }
+}
+
+func (content *Content) GetReplyTo() string {
+ if content.RelatesTo != nil {
+ return content.RelatesTo.InReplyTo.EventID
+ }
+ return ""
+}
+
+const ReplyFormat = `<mx-reply><blockquote>
+<a href="https://matrix.to/#/%s/%s">In reply to</a>
+<a href="https://matrix.to/#/%s">%s</a>
+%s
+</blockquote></mx-reply>
+`
+
+func (evt *Event) GenerateReplyFallbackHTML() string {
+ body := evt.Content.FormattedBody
+ if len(body) == 0 {
+ body = html.EscapeString(evt.Content.Body)
+ }
+
+ senderDisplayName := evt.Sender
+
+ return fmt.Sprintf(ReplyFormat, evt.RoomID, evt.ID, evt.Sender, senderDisplayName, body)
+}
+
+func (evt *Event) GenerateReplyFallbackText() string {
+ body := evt.Content.Body
+ lines := strings.Split(strings.TrimSpace(body), "\n")
+ firstLine, lines := lines[0], lines[1:]
+
+ senderDisplayName := evt.Sender
+
+ var fallbackText strings.Builder
+ fmt.Fprintf(&fallbackText, "> <%s> %s", senderDisplayName, firstLine)
+ for _, line := range lines {
+ fmt.Fprintf(&fallbackText, "\n> %s", line)
+ }
+ fallbackText.WriteString("\n\n")
+ return fallbackText.String()
+}
+
+func (content *Content) SetReply(inReplyTo *Event) {
+ if content.RelatesTo == nil {
+ content.RelatesTo = &RelatesTo{}
+ }
+ content.RelatesTo.InReplyTo = InReplyTo{
+ EventID: inReplyTo.ID,
+ RoomID: inReplyTo.RoomID,
+ }
+
+ if content.MsgType == MsgText || content.MsgType == MsgNotice {
+ if len(content.FormattedBody) == 0 || content.Format != FormatHTML {
+ content.FormattedBody = html.EscapeString(content.Body)
+ content.Format = FormatHTML
+ }
+ content.FormattedBody = inReplyTo.GenerateReplyFallbackHTML() + content.FormattedBody
+ content.Body = inReplyTo.GenerateReplyFallbackText() + content.Body
+ }
+}
diff --git a/vendor/maunium.net/go/gomatrix/requests.go b/vendor/maunium.net/go/gomatrix/requests.go
index af99a22..d8e10a6 100644
--- a/vendor/maunium.net/go/gomatrix/requests.go
+++ b/vendor/maunium.net/go/gomatrix/requests.go
@@ -31,7 +31,7 @@ type ReqCreateRoom struct {
Invite []string `json:"invite,omitempty"`
Invite3PID []ReqInvite3PID `json:"invite_3pid,omitempty"`
CreationContent map[string]interface{} `json:"creation_content,omitempty"`
- InitialState []Event `json:"initial_state,omitempty"`
+ InitialState []*Event `json:"initial_state,omitempty"`
Preset string `json:"preset,omitempty"`
IsDirect bool `json:"is_direct,omitempty"`
}
@@ -74,5 +74,9 @@ type ReqUnbanUser struct {
// ReqTyping is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
type ReqTyping struct {
Typing bool `json:"typing"`
- Timeout int64 `json:"timeout"`
+ Timeout int64 `json:"timeout,omitempty"`
}
+
+type ReqPresence struct {
+ Presence string `json:"presence"`
+} \ No newline at end of file
diff --git a/vendor/maunium.net/go/gomatrix/responses.go b/vendor/maunium.net/go/gomatrix/responses.go
index 6d43bd3..9524d62 100644
--- a/vendor/maunium.net/go/gomatrix/responses.go
+++ b/vendor/maunium.net/go/gomatrix/responses.go
@@ -64,7 +64,7 @@ type RespJoinedMembers struct {
// RespMessages is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages
type RespMessages struct {
Start string `json:"start"`
- Chunk []Event `json:"chunk"`
+ Chunk []*Event `json:"chunk"`
End string `json:"end"`
}
diff --git a/vendor/maunium.net/go/gomatrix/room.go b/vendor/maunium.net/go/gomatrix/room.go
index c9b2351..80a91d8 100644
--- a/vendor/maunium.net/go/gomatrix/room.go
+++ b/vendor/maunium.net/go/gomatrix/room.go
@@ -3,7 +3,7 @@ package gomatrix
// Room represents a single Matrix room.
type Room struct {
ID string
- State map[string]map[string]*Event
+ State map[EventType]map[string]*Event
}
// UpdateState updates the room's current state with the given Event. This will clobber events based
@@ -17,7 +17,7 @@ func (room Room) UpdateState(event *Event) {
}
// GetStateEvent returns the state event for the given type/state_key combo, or nil.
-func (room Room) GetStateEvent(eventType string, stateKey string) *Event {
+func (room Room) GetStateEvent(eventType EventType, stateKey string) *Event {
stateEventMap, _ := room.State[eventType]
event, _ := stateEventMap[stateKey]
return event
@@ -25,17 +25,11 @@ func (room Room) GetStateEvent(eventType string, stateKey string) *Event {
// GetMembershipState returns the membership state of the given user ID in this room. If there is
// no entry for this member, 'leave' is returned for consistency with left users.
-func (room Room) GetMembershipState(userID string) string {
- state := "leave"
- event := room.GetStateEvent("m.room.member", userID)
+func (room Room) GetMembershipState(userID string) Membership {
+ state := MembershipLeave
+ event := room.GetStateEvent(StateMember, userID)
if event != nil {
- membershipState, found := event.Content["membership"]
- if found {
- mState, isString := membershipState.(string)
- if isString {
- state = mState
- }
- }
+ state = event.Content.Membership
}
return state
}
@@ -45,6 +39,6 @@ func NewRoom(roomID string) *Room {
// Init the State map and return a pointer to the Room
return &Room{
ID: roomID,
- State: make(map[string]map[string]*Event),
+ State: make(map[EventType]map[string]*Event),
}
}
diff --git a/vendor/maunium.net/go/gomatrix/sync.go b/vendor/maunium.net/go/gomatrix/sync.go
index e1233a4..09170d7 100644
--- a/vendor/maunium.net/go/gomatrix/sync.go
+++ b/vendor/maunium.net/go/gomatrix/sync.go
@@ -25,7 +25,7 @@ type Syncer interface {
type DefaultSyncer struct {
UserID string
Store Storer
- listeners map[string][]OnEventListener // event type to listeners array
+ listeners map[EventType][]OnEventListener // event type to listeners array
}
// OnEventListener can be used with DefaultSyncer.OnEventType to be informed of incoming events.
@@ -36,7 +36,7 @@ func NewDefaultSyncer(userID string, store Storer) *DefaultSyncer {
return &DefaultSyncer{
UserID: userID,
Store: store,
- listeners: make(map[string][]OnEventListener),
+ listeners: make(map[EventType][]OnEventListener),
}
}
@@ -88,7 +88,7 @@ func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error)
// OnEventType allows callers to be notified when there are new events for the given event type.
// There are no duplicate checks.
-func (s *DefaultSyncer) OnEventType(eventType string, callback OnEventListener) {
+func (s *DefaultSyncer) OnEventType(eventType EventType, callback OnEventListener) {
_, exists := s.listeners[eventType]
if !exists {
s.listeners[eventType] = []OnEventListener{}
@@ -112,13 +112,8 @@ func (s *DefaultSyncer) shouldProcessResponse(resp *RespSync, since string) bool
for roomID, roomData := range resp.Rooms.Join {
for i := len(roomData.Timeline.Events) - 1; i >= 0; i-- {
e := roomData.Timeline.Events[i]
- if e.Type == "m.room.member" && e.StateKey != nil && *e.StateKey == s.UserID {
- m := e.Content["membership"]
- mship, ok := m.(string)
- if !ok {
- continue
- }
- if mship == "join" {
+ if e.Type == StateMember && e.GetStateKey() == s.UserID {
+ if e.Content.Membership == "join" {
_, ok := resp.Rooms.Join[roomID]
if !ok {
continue