From 9818f322037321e97c8096bb4653b48668b65e49 Mon Sep 17 00:00:00 2001 From: Simon THOBY Date: Thu, 8 Jul 2021 23:35:37 +0200 Subject: [PATCH] WIP: mautrix --- bridge/matrix/helpers.go | 112 +- bridge/matrix/matrix.go | 486 +++--- go.mod | 3 +- go.sum | 49 +- vendor/github.com/btcsuite/btcutil/LICENSE | 16 + .../btcsuite/btcutil/base58/README.md | 34 + .../btcsuite/btcutil/base58/alphabet.go | 49 + .../btcsuite/btcutil/base58/base58.go | 75 + .../btcsuite/btcutil/base58/base58check.go | 52 + .../btcsuite/btcutil/base58/cov_report.sh | 17 + .../github.com/btcsuite/btcutil/base58/doc.go | 29 + .../github.com/matrix-org/gomatrix/.gitignore | 28 - .../matrix-org/gomatrix/.golangci.yml | 21 - .../matrix-org/gomatrix/.travis.yml | 7 - .../matrix-org/gomatrix/CHANGELOG.md | 1 - vendor/github.com/matrix-org/gomatrix/LICENSE | 201 --- .../github.com/matrix-org/gomatrix/README.md | 71 - .../github.com/matrix-org/gomatrix/client.go | 805 ---------- .../github.com/matrix-org/gomatrix/events.go | 157 -- .../github.com/matrix-org/gomatrix/filter.go | 90 -- vendor/github.com/matrix-org/gomatrix/go.mod | 3 - .../matrix-org/gomatrix/identifier.go | 69 - .../matrix-org/gomatrix/requests.go | 79 - .../matrix-org/gomatrix/responses.go | 210 --- vendor/github.com/matrix-org/gomatrix/room.go | 63 - .../github.com/matrix-org/gomatrix/store.go | 65 - vendor/github.com/matrix-org/gomatrix/sync.go | 168 --- vendor/github.com/matrix-org/gomatrix/tags.go | 26 - vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go | 77 + vendor/maunium.net/go/mautrix/.gitignore | 2 + vendor/maunium.net/go/mautrix/LICENSE | 374 +++++ vendor/maunium.net/go/mautrix/README.md | 24 + vendor/maunium.net/go/mautrix/client.go | 1344 +++++++++++++++++ .../mautrix/crypto/attachment/attachments.go | 171 +++ .../go/mautrix/crypto/utils/utils.go | 133 ++ vendor/maunium.net/go/mautrix/error.go | 144 ++ .../go/mautrix/event/accountdata.go | 45 + .../maunium.net/go/mautrix/event/content.go | 457 ++++++ .../go/mautrix/event/encryption.go | 141 ++ .../maunium.net/go/mautrix/event/ephemeral.go | 80 + vendor/maunium.net/go/mautrix/event/events.go | 54 + vendor/maunium.net/go/mautrix/event/member.go | 53 + .../maunium.net/go/mautrix/event/message.go | 231 +++ .../go/mautrix/event/powerlevels.go | 128 ++ .../maunium.net/go/mautrix/event/relations.go | 200 +++ vendor/maunium.net/go/mautrix/event/reply.go | 108 ++ vendor/maunium.net/go/mautrix/event/state.go | 110 ++ vendor/maunium.net/go/mautrix/event/type.go | 235 +++ .../go/mautrix/event/verification.go | 306 ++++ vendor/maunium.net/go/mautrix/event/voip.go | 116 ++ vendor/maunium.net/go/mautrix/filter.go | 93 ++ vendor/maunium.net/go/mautrix/go.mod | 19 + vendor/maunium.net/go/mautrix/go.sum | 77 + .../maunium.net/go/mautrix/id/contenturi.go | 133 ++ vendor/maunium.net/go/mautrix/id/crypto.go | 114 ++ vendor/maunium.net/go/mautrix/id/matrixuri.go | 293 ++++ vendor/maunium.net/go/mautrix/id/opaque.go | 41 + .../go/mautrix/id/userid.go} | 97 +- .../go/mautrix/pushrules/action.go | 124 ++ .../go/mautrix/pushrules/condition.go | 149 ++ .../maunium.net/go/mautrix/pushrules/doc.go | 2 + .../go/mautrix/pushrules/glob/LICENSE | 22 + .../go/mautrix/pushrules/glob/README.md | 28 + .../go/mautrix/pushrules/glob/glob.go | 108 ++ .../go/mautrix/pushrules/pushrules.go | 37 + .../maunium.net/go/mautrix/pushrules/rule.go | 154 ++ .../go/mautrix/pushrules/ruleset.go | 88 ++ vendor/maunium.net/go/mautrix/requests.go | 302 ++++ vendor/maunium.net/go/mautrix/responses.go | 291 ++++ vendor/maunium.net/go/mautrix/room.go | 52 + vendor/maunium.net/go/mautrix/store.go | 162 ++ vendor/maunium.net/go/mautrix/sync.go | 283 ++++ vendor/maunium.net/go/mautrix/version.go | 5 + vendor/modules.txt | 13 +- 74 files changed, 7740 insertions(+), 2466 deletions(-) create mode 100644 vendor/github.com/btcsuite/btcutil/LICENSE create mode 100644 vendor/github.com/btcsuite/btcutil/base58/README.md create mode 100644 vendor/github.com/btcsuite/btcutil/base58/alphabet.go create mode 100644 vendor/github.com/btcsuite/btcutil/base58/base58.go create mode 100644 vendor/github.com/btcsuite/btcutil/base58/base58check.go create mode 100644 vendor/github.com/btcsuite/btcutil/base58/cov_report.sh create mode 100644 vendor/github.com/btcsuite/btcutil/base58/doc.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/.gitignore delete mode 100644 vendor/github.com/matrix-org/gomatrix/.golangci.yml delete mode 100644 vendor/github.com/matrix-org/gomatrix/.travis.yml delete mode 100644 vendor/github.com/matrix-org/gomatrix/CHANGELOG.md delete mode 100644 vendor/github.com/matrix-org/gomatrix/LICENSE delete mode 100644 vendor/github.com/matrix-org/gomatrix/README.md delete mode 100644 vendor/github.com/matrix-org/gomatrix/client.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/events.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/filter.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/go.mod delete mode 100644 vendor/github.com/matrix-org/gomatrix/identifier.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/requests.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/responses.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/room.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/store.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/sync.go delete mode 100644 vendor/github.com/matrix-org/gomatrix/tags.go create mode 100644 vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go create mode 100644 vendor/maunium.net/go/mautrix/.gitignore create mode 100644 vendor/maunium.net/go/mautrix/LICENSE create mode 100644 vendor/maunium.net/go/mautrix/README.md create mode 100644 vendor/maunium.net/go/mautrix/client.go create mode 100644 vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go create mode 100644 vendor/maunium.net/go/mautrix/crypto/utils/utils.go create mode 100644 vendor/maunium.net/go/mautrix/error.go create mode 100644 vendor/maunium.net/go/mautrix/event/accountdata.go create mode 100644 vendor/maunium.net/go/mautrix/event/content.go create mode 100644 vendor/maunium.net/go/mautrix/event/encryption.go create mode 100644 vendor/maunium.net/go/mautrix/event/ephemeral.go create mode 100644 vendor/maunium.net/go/mautrix/event/events.go create mode 100644 vendor/maunium.net/go/mautrix/event/member.go create mode 100644 vendor/maunium.net/go/mautrix/event/message.go create mode 100644 vendor/maunium.net/go/mautrix/event/powerlevels.go create mode 100644 vendor/maunium.net/go/mautrix/event/relations.go create mode 100644 vendor/maunium.net/go/mautrix/event/reply.go create mode 100644 vendor/maunium.net/go/mautrix/event/state.go create mode 100644 vendor/maunium.net/go/mautrix/event/type.go create mode 100644 vendor/maunium.net/go/mautrix/event/verification.go create mode 100644 vendor/maunium.net/go/mautrix/event/voip.go create mode 100644 vendor/maunium.net/go/mautrix/filter.go create mode 100644 vendor/maunium.net/go/mautrix/go.mod create mode 100644 vendor/maunium.net/go/mautrix/go.sum create mode 100644 vendor/maunium.net/go/mautrix/id/contenturi.go create mode 100644 vendor/maunium.net/go/mautrix/id/crypto.go create mode 100644 vendor/maunium.net/go/mautrix/id/matrixuri.go create mode 100644 vendor/maunium.net/go/mautrix/id/opaque.go rename vendor/{github.com/matrix-org/gomatrix/userids.go => maunium.net/go/mautrix/id/userid.go} (57%) create mode 100644 vendor/maunium.net/go/mautrix/pushrules/action.go create mode 100644 vendor/maunium.net/go/mautrix/pushrules/condition.go create mode 100644 vendor/maunium.net/go/mautrix/pushrules/doc.go create mode 100644 vendor/maunium.net/go/mautrix/pushrules/glob/LICENSE create mode 100644 vendor/maunium.net/go/mautrix/pushrules/glob/README.md create mode 100644 vendor/maunium.net/go/mautrix/pushrules/glob/glob.go create mode 100644 vendor/maunium.net/go/mautrix/pushrules/pushrules.go create mode 100644 vendor/maunium.net/go/mautrix/pushrules/rule.go create mode 100644 vendor/maunium.net/go/mautrix/pushrules/ruleset.go create mode 100644 vendor/maunium.net/go/mautrix/requests.go create mode 100644 vendor/maunium.net/go/mautrix/responses.go create mode 100644 vendor/maunium.net/go/mautrix/room.go create mode 100644 vendor/maunium.net/go/mautrix/store.go create mode 100644 vendor/maunium.net/go/mautrix/sync.go create mode 100644 vendor/maunium.net/go/mautrix/version.go diff --git a/bridge/matrix/helpers.go b/bridge/matrix/helpers.go index 71a15ed2..e0ed4bae 100644 --- a/bridge/matrix/helpers.go +++ b/bridge/matrix/helpers.go @@ -1,14 +1,13 @@ package bmatrix import ( - "encoding/json" "errors" "fmt" "html" - "strings" "time" - matrix "github.com/matrix-org/gomatrix" + matrix "maunium.net/go/mautrix" + "maunium.net/go/mautrix/id" ) func newMatrixUsername(username string) *matrixUsername { @@ -28,7 +27,7 @@ func newMatrixUsername(username string) *matrixUsername { } // getRoomID retrieves a matching room ID from the channel name. -func (b *Bmatrix) getRoomID(channel string) string { +func (b *Bmatrix) getRoomID(channel string) id.RoomID { b.RLock() defer b.RUnlock() for ID, name := range b.RoomMap { @@ -40,21 +39,10 @@ func (b *Bmatrix) getRoomID(channel string) string { return "" } -// interface2Struct marshals and immediately unmarshals an interface. -// Useful for converting map[string]interface{} to a struct. -func interface2Struct(in interface{}, out interface{}) error { - jsonObj, err := json.Marshal(in) - if err != nil { - return err //nolint:wrapcheck - } - - return json.Unmarshal(jsonObj, out) -} - // getDisplayName retrieves the displayName for mxid, querying the homeserver if the mxid is not in the cache. -func (b *Bmatrix) getDisplayName(channelID string, mxid string) string { +func (b *Bmatrix) getDisplayName(channelID id.RoomID, mxid id.UserID) string { if b.GetBool("UseUserName") { - return mxid[1:] + return string(mxid)[1:] } b.RLock() @@ -74,7 +62,7 @@ func (b *Bmatrix) getDisplayName(channelID string, mxid string) string { } if err != nil { - return b.cacheDisplayName(channelID, mxid, mxid[1:]) + return b.cacheDisplayName(channelID, mxid, string(mxid)[1:]) } return b.cacheDisplayName(channelID, mxid, displayName.DisplayName) @@ -82,12 +70,12 @@ func (b *Bmatrix) getDisplayName(channelID string, mxid string) string { // cacheDisplayName stores the mapping between a mxid and a display name, to be reused later without performing a query to the homeserver. // Note that old entries are cleaned when this function is called. -func (b *Bmatrix) cacheDisplayName(channelID string, mxid string, displayName string) string { +func (b *Bmatrix) cacheDisplayName(channelID id.RoomID, mxid id.UserID, displayName string) string { now := time.Now() // scan to delete old entries, to stop memory usage from becoming high with obsolete entries. // In addition, we detect if another user have the same username, and if so, we append their mxids to their usernames to differentiate them. - toDelete := map[string]string{} + toDelete := map[id.RoomID]id.UserID{} conflict := false b.Lock() @@ -118,7 +106,7 @@ func (b *Bmatrix) cacheDisplayName(channelID string, mxid string, displayName st } if _, channelPresent := b.NicknameMap[channelID]; !channelPresent { - b.NicknameMap[channelID] = make(map[string]NicknameCacheEntry) + b.NicknameMap[channelID] = make(map[id.UserID]NicknameCacheEntry) } b.NicknameMap[channelID][mxid] = NicknameCacheEntry{ @@ -130,77 +118,43 @@ func (b *Bmatrix) cacheDisplayName(channelID string, mxid string, displayName st return displayName } -// handleError converts errors into httpError. -//nolint:exhaustivestruct -func handleError(err error) *httpError { - var mErr matrix.HTTPError - if !errors.As(err, &mErr) { - return &httpError{ - Err: "not a HTTPError", - } - } - - var httpErr httpError - - if err := json.Unmarshal(mErr.Contents, &httpErr); err != nil { - return &httpError{ - Err: "unmarshal failed", - } - } - - return &httpErr -} - -func (b *Bmatrix) containsAttachment(content map[string]interface{}) bool { - // Skip empty messages - if content["msgtype"] == nil { - return false - } - - // Only allow image,video or file msgtypes - if !(content["msgtype"].(string) == "m.image" || - content["msgtype"].(string) == "m.video" || - content["msgtype"].(string) == "m.file") { - return false - } - - return true -} - // getAvatarURL returns the avatar URL of the specified sender. -func (b *Bmatrix) getAvatarURL(sender string) string { - urlPath := b.mc.BuildURL("profile", sender, "avatar_url") - - s := struct { - AvatarURL string `json:"avatar_url"` - }{} - - err := b.mc.MakeRequest("GET", urlPath, nil, &s) +func (b *Bmatrix) getAvatarURL(sender id.UserID) string { + url, err := b.mc.GetAvatarURL(sender) if err != nil { - b.Log.Errorf("getAvatarURL failed: %s", err) - + b.Log.Errorf("Couldn't retrieve the URL of the avatar for MXID %s", sender) return "" } - url := strings.ReplaceAll(s.AvatarURL, "mxc://", b.GetString("Server")+"/_matrix/media/r0/thumbnail/") - if url != "" { - url += "?width=37&height=37&method=crop" - } - - return url + return url.String() } // handleRatelimit handles the ratelimit errors and return if we're ratelimited and the amount of time to sleep func (b *Bmatrix) handleRatelimit(err error) (time.Duration, bool) { - httpErr := handleError(err) - if httpErr.Errcode != "M_LIMIT_EXCEEDED" { + var mErr matrix.HTTPError + if !errors.As(err, &mErr) { + b.Log.Errorf("Received a non-HTTPError, don't know what to make of it:\n%#v", err) return 0, false } - b.Log.Debugf("ratelimited: %s", httpErr.Err) - b.Log.Infof("getting ratelimited by matrix, sleeping approx %d seconds before retrying", httpErr.RetryAfterMs/1000) + if mErr.RespError.ErrCode != "M_LIMIT_EXCEEDED" { + return 0, false + } - return time.Duration(httpErr.RetryAfterMs) * time.Millisecond, true + b.Log.Debugf("ratelimited: %s", mErr.RespError.Err) + + // fallback to a one-second delay + retryDelayMs := 1000 + + if retryDelayString, present := mErr.RespError.ExtraData["retry_after_ms"]; present { + if retryDelayInt, correct := retryDelayString.(int); correct && retryDelayInt > retryDelayMs { + retryDelayMs = retryDelayInt + } + } + + b.Log.Infof("getting ratelimited by matrix, sleeping approx %d seconds before retrying", retryDelayMs/1000) + + return time.Duration(retryDelayMs) * time.Millisecond, true } // retry function will check if we're ratelimited and retries again when backoff time expired diff --git a/bridge/matrix/matrix.go b/bridge/matrix/matrix.go index 7541174d..4bf9cb50 100644 --- a/bridge/matrix/matrix.go +++ b/bridge/matrix/matrix.go @@ -1,3 +1,4 @@ +//nolint: exhaustivestruct package bmatrix import ( @@ -9,10 +10,13 @@ import ( "sync" "time" + matrix "maunium.net/go/mautrix" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" + "github.com/42wim/matterbridge/bridge" "github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/helper" - matrix "github.com/matrix-org/gomatrix" ) var ( @@ -26,20 +30,15 @@ type NicknameCacheEntry struct { } type Bmatrix struct { - mc *matrix.Client - UserID string - // channelId -> mxid -> NickNameCacheEntry - NicknameMap map[string]map[string]NicknameCacheEntry - RoomMap map[string]string + mc *matrix.Client + UserID id.UserID + NicknameMap map[id.RoomID]map[id.UserID]NicknameCacheEntry + RoomMap map[id.RoomID]string rateMutex sync.RWMutex sync.RWMutex *bridge.Config -} - -type httpError struct { - Errcode string `json:"errcode"` - Err string `json:"error"` - RetryAfterMs int `json:"retry_after_ms"` + stop chan int + stopAck chan int } type matrixUsername struct { @@ -47,29 +46,12 @@ type matrixUsername struct { formatted string } -// SubTextMessage represents the new content of the message in edit messages. -type SubTextMessage struct { - MsgType string `json:"msgtype"` - Body string `json:"body"` -} - -// MessageRelation explains how the current message relates to a previous message. -// Notably used for message edits. -type MessageRelation struct { - EventID string `json:"event_id"` - Type string `json:"rel_type"` -} - -type EditedMessage struct { - NewContent SubTextMessage `json:"m.new_content"` - RelatedTo MessageRelation `json:"m.relates_to"` - matrix.TextMessage -} - func New(cfg *bridge.Config) bridge.Bridger { b := &Bmatrix{Config: cfg} - b.RoomMap = make(map[string]string) - b.NicknameMap = make(map[string]map[string]NicknameCacheEntry) + b.RoomMap = make(map[id.RoomID]string) + b.NicknameMap = make(map[id.RoomID]map[id.UserID]NicknameCacheEntry) + b.stop = make(chan int, 1) + b.stopAck = make(chan int, 1) return b } @@ -77,13 +59,13 @@ func (b *Bmatrix) Connect() error { var err error b.Log.Infof("Connecting %s", b.GetString("Server")) if b.GetString("MxID") != "" && b.GetString("Token") != "" { + b.UserID = id.UserID(b.GetString("MxID")) b.mc, err = matrix.NewClient( - b.GetString("Server"), b.GetString("MxID"), b.GetString("Token"), + b.GetString("Server"), b.UserID, b.GetString("Token"), ) if err != nil { return err } - b.UserID = b.GetString("MxID") b.Log.Info("Using existing Matrix credentials") } else { b.mc, err = matrix.NewClient(b.GetString("Server"), "", "") @@ -91,15 +73,14 @@ func (b *Bmatrix) Connect() error { return err } resp, err := b.mc.Login(&matrix.ReqLogin{ - Type: "m.login.password", - User: b.GetString("Login"), - Password: b.GetString("Password"), - Identifier: matrix.NewUserIdentifier(b.GetString("Login")), + Type: matrix.AuthTypePassword, + Password: b.GetString("Password"), + Identifier: matrix.UserIdentifier{Type: matrix.IdentifierTypeUser, User: b.GetString("Login")}, + StoreCredentials: true, }) if err != nil { return err } - b.mc.SetCredentials(resp.UserID, resp.AccessToken) b.UserID = resp.UserID b.Log.Info("Connection succeeded") } @@ -108,6 +89,13 @@ func (b *Bmatrix) Connect() error { } func (b *Bmatrix) Disconnect() error { + // tell the Sync() loop to exit + b.stop <- 0 + b.mc.StopSync() + + // wait for the syncer to terminate + <-b.stopAck + return nil } @@ -136,26 +124,13 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { // Make a action /me of the message if msg.Event == config.EventUserAction { - m := matrix.TextMessage{ - MsgType: "m.emote", + m := event.MessageEventContent{ + MsgType: event.MsgEmote, Body: username.plain + msg.Text, FormattedBody: username.formatted + msg.Text, } - msgID := "" - - err := b.retry(func() error { - resp, err := b.mc.SendMessageEvent(channel, "m.room.message", m) - if err != nil { - return err - } - - msgID = resp.EventID - - return err - }) - - return msgID, err + return b.sendEventWithRetries(channel, event.EventMessage, m) } // Delete message @@ -167,12 +142,12 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { msgID := "" err := b.retry(func() error { - resp, err := b.mc.RedactEvent(channel, msg.ID, &matrix.ReqRedact{}) + resp, err := b.mc.RedactEvent(channel, id.EventID(msg.ID), matrix.ReqRedact{}) if err != nil { return err } - msgID = resp.EventID + msgID = string(resp.EventID) return err }) @@ -183,13 +158,12 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { // Upload a file if it exists if msg.Extra != nil { for _, rmsg := range helper.HandleExtra(&msg, b.General) { - rmsg := rmsg + m := event.MessageEventContent{ + MsgType: event.MsgText, + Body: rmsg.Username + rmsg.Text, + } - err := b.retry(func() error { - _, err := b.mc.SendText(channel, rmsg.Username+rmsg.Text) - - return err - }) + _, err := b.sendEventWithRetries(channel, event.EventMessage, m) if err != nil { b.Log.Errorf("sendText failed: %s", err) } @@ -202,89 +176,84 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { // Edit message if we have an ID if msg.ID != "" { - rmsg := EditedMessage{TextMessage: matrix.TextMessage{ + rmsg := event.MessageEventContent{ Body: username.plain + msg.Text, - MsgType: "m.text", - }} + MsgType: event.MsgText, + } if b.GetBool("HTMLDisable") { - rmsg.TextMessage.FormattedBody = username.formatted + "* " + msg.Text + rmsg.FormattedBody = username.formatted + "* " + msg.Text } else { - rmsg.Format = "org.matrix.custom.html" - rmsg.TextMessage.FormattedBody = username.formatted + "* " + helper.ParseMarkdown(msg.Text) + rmsg.Format = event.FormatHTML + rmsg.FormattedBody = username.formatted + "* " + helper.ParseMarkdown(msg.Text) } - rmsg.NewContent = SubTextMessage{ - Body: rmsg.TextMessage.Body, - MsgType: "m.text", + rmsg.NewContent = &event.MessageEventContent{ + Body: rmsg.Body, + MsgType: event.MsgText, } - rmsg.RelatedTo = MessageRelation{ - EventID: msg.ID, - Type: "m.replace", + rmsg.RelatesTo = &event.RelatesTo{ + EventID: id.EventID(msg.ID), + Type: event.RelReplace, } - err := b.retry(func() error { - _, err := b.mc.SendMessageEvent(channel, "m.room.message", rmsg) + return b.sendEventWithRetries(channel, event.EventMessage, rmsg) + } - return err - }) - if err != nil { - return "", err - } - - return msg.ID, nil + m := event.MessageEventContent{ + Body: username.plain + msg.Text, + FormattedBody: username.formatted + msg.Text, } // Use notices to send join/leave events if msg.Event == config.EventJoinLeave { - m := matrix.TextMessage{ - MsgType: "m.notice", - Body: username.plain + msg.Text, - FormattedBody: username.formatted + msg.Text, + m.MsgType = event.MsgNotice + } else { + m.MsgType = event.MsgText + if b.GetBool("HTMLDisable") { + m.FormattedBody = "" + } else { + m.FormattedBody = username.formatted + helper.ParseMarkdown(msg.Text) } - - var ( - resp *matrix.RespSendEvent - err error - ) - - err = b.retry(func() error { - resp, err = b.mc.SendMessageEvent(channel, "m.room.message", m) - - return err - }) - if err != nil { - return "", err - } - - return resp.EventID, err } - if b.GetBool("HTMLDisable") { - var ( - resp *matrix.RespSendEvent - err error - ) + return b.sendEventWithRetries(channel, event.EventMessage, m) +} - err = b.retry(func() error { - resp, err = b.mc.SendText(channel, username.plain+msg.Text) +func (b *Bmatrix) handlematrix() { + syncer, ok := b.mc.Syncer.(*matrix.DefaultSyncer) + if !ok { + b.Log.Errorln("couldn't convert the Syncer object to a DefaultSyncer structure, the matrix bridge won't work") - return err - }) - if err != nil { - return "", err - } - - return resp.EventID, err + return } - // Post normal message with HTML support (eg riot.im) + syncer.OnEventType(event.EventRedaction, b.handleEvent) + syncer.OnEventType(event.EventMessage, b.handleEvent) + syncer.OnEventType(event.StateMember, b.handleMemberChange) + go func() { + for { + select { + case <-b.stop: + b.stopAck <- 1 + + return + default: + if err := b.mc.Sync(); err != nil { + b.Log.Println("Sync() returned ", err) + } + } + } + }() +} + +//nolint: wrapcheck +func (b *Bmatrix) sendEventWithRetries(channel id.RoomID, eventType event.Type, eventData interface{}) (string, error) { var ( resp *matrix.RespSendEvent err error ) err = b.retry(func() error { - resp, err = b.mc.SendFormattedText(channel, username.plain+msg.Text, - username.formatted+helper.ParseMarkdown(msg.Text)) + resp, err = b.mc.SendMessageEvent(channel, eventType, eventData) return err }) @@ -292,63 +261,57 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { return "", err } - return resp.EventID, err + return string(resp.EventID), err } -func (b *Bmatrix) handlematrix() { - syncer := b.mc.Syncer.(*matrix.DefaultSyncer) - syncer.OnEventType("m.room.redaction", b.handleEvent) - syncer.OnEventType("m.room.message", b.handleEvent) - syncer.OnEventType("m.room.member", b.handleMemberChange) - go func() { - for { - if err := b.mc.Sync(); err != nil { - b.Log.Println("Sync() returned ", err) - } +func (b *Bmatrix) handleMemberChange(source matrix.EventSource, ev *event.Event) { + member := ev.Content.AsMember() + if member == nil { + b.Log.Errorf("Couldn't process a member event:\n%#v", ev) + + return + } + // Update the displayname on join messages, according to https://matrix.org/docs/spec/client_server/r0.6.1#events-on-change-of-profile-information + if member.Membership == event.MembershipJoin { + b.cacheDisplayName(ev.RoomID, ev.Sender, member.Displayname) + } +} + +func (b *Bmatrix) handleMessage(rmsg config.Message, msg event.MessageEventContent, ev *event.Event) { + rmsg.Text = msg.Body + + // Do we have a /me action + if msg.MsgType == event.MsgEmote { + rmsg.Event = config.EventUserAction + } + + // Is it an edit? + if msg.RelatesTo != nil && msg.NewContent != nil && msg.RelatesTo.Type == event.RelReplace { + rmsg.ID = string(msg.RelatesTo.EventID) + rmsg.Text = msg.NewContent.Body + b.Remote <- rmsg + + return + } + + // Do we have attachments (we only allow image,video or file msgtypes) + if msg.MsgType == event.MsgImage || msg.MsgType == event.MsgVideo || msg.MsgType == event.MsgFile { + err := b.handleDownloadFile(&rmsg, msg) + if err != nil { + b.Log.Errorf("download failed: %#v", err) } - }() -} - -func (b *Bmatrix) handleEdit(ev *matrix.Event, rmsg config.Message) bool { - relationInterface, present := ev.Content["m.relates_to"] - newContentInterface, present2 := ev.Content["m.new_content"] - if !(present && present2) { - return false } - var relation MessageRelation - if err := interface2Struct(relationInterface, &relation); err != nil { - b.Log.Warnf("Couldn't parse 'm.relates_to' object with value %#v", relationInterface) - return false - } - - var newContent SubTextMessage - if err := interface2Struct(newContentInterface, &newContent); err != nil { - b.Log.Warnf("Couldn't parse 'm.new_content' object with value %#v", newContentInterface) - return false - } - - if relation.Type != "m.replace" { - return false - } - - rmsg.ID = relation.EventID - rmsg.Text = newContent.Body + b.Log.Debugf("<= Sending message from %s on %s to gateway", ev.Sender, b.Account) b.Remote <- rmsg - return true -} - -func (b *Bmatrix) handleMemberChange(ev *matrix.Event) { - // Update the displayname on join messages, according to https://matrix.org/docs/spec/client_server/r0.6.1#events-on-change-of-profile-information - if ev.Content["membership"] == "join" { - if dn, ok := ev.Content["displayname"].(string); ok { - b.cacheDisplayName(ev.RoomID, ev.Sender, dn) - } + // not crucial, so no ratelimit check here + if err := b.mc.MarkRead(ev.RoomID, ev.ID); err != nil { + b.Log.Errorf("couldn't mark message as read %s", err.Error()) } } -func (b *Bmatrix) handleEvent(ev *matrix.Event) { +func (b *Bmatrix) handleEvent(source matrix.EventSource, ev *event.Event) { b.Log.Debugf("== Receiving event: %#v", ev) if ev.Sender != b.UserID { b.RLock() @@ -364,8 +327,8 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) { Username: b.getDisplayName(ev.RoomID, ev.Sender), Channel: channel, Account: b.Account, - UserID: ev.Sender, - ID: ev.ID, + UserID: string(ev.Sender), + ID: string(ev.ID), Avatar: b.getAvatarURL(ev.Sender), } @@ -376,95 +339,52 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) { } // Delete event - if ev.Type == "m.room.redaction" { + if ev.Type == event.EventRedaction { rmsg.Event = config.EventMsgDelete - rmsg.ID = ev.Redacts + rmsg.ID = string(ev.Redacts) rmsg.Text = config.EventMsgDelete b.Remote <- rmsg return } - // Text must be a string - if rmsg.Text, ok = ev.Content["body"].(string); !ok { - b.Log.Errorf("Content[body] is not a string: %T\n%#v", - ev.Content["body"], ev.Content) + msg := ev.Content.AsMessage() + if msg == nil { + b.Log.Errorf("matterbridge don't support this event type: %s", ev.Type.Type) + b.Log.Debugf("Full event: %#v", ev) + return } - // Do we have a /me action - if ev.Content["msgtype"].(string) == "m.emote" { - rmsg.Event = config.EventUserAction - } - - // Is it an edit? - if b.handleEdit(ev, rmsg) { - return - } - - // Do we have attachments - if b.containsAttachment(ev.Content) { - err := b.handleDownloadFile(&rmsg, ev.Content) - if err != nil { - b.Log.Errorf("download failed: %#v", err) - } - } - - b.Log.Debugf("<= Sending message from %s on %s to gateway", ev.Sender, b.Account) - b.Remote <- rmsg - - // not crucial, so no ratelimit check here - if err := b.mc.MarkRead(ev.RoomID, ev.ID); err != nil { - b.Log.Errorf("couldn't mark message as read %s", err.Error()) - } + b.handleMessage(rmsg, *msg, ev) } } // handleDownloadFile handles file download -func (b *Bmatrix) handleDownloadFile(rmsg *config.Message, content map[string]interface{}) error { - var ( - ok bool - url, name, msgtype, mtype string - info map[string]interface{} - size float64 - ) - +func (b *Bmatrix) handleDownloadFile(rmsg *config.Message, msg event.MessageEventContent) error { rmsg.Extra = make(map[string][]interface{}) - if url, ok = content["url"].(string); !ok { - return fmt.Errorf("url isn't a %T", url) + if msg.URL == "" || msg.Info == nil { + b.Log.Error("couldn't download a file with no URL or no file informations (invalid event ?)") + b.Log.Debugf("Full Message content:\n%#v", msg) } - url = strings.Replace(url, "mxc://", b.GetString("Server")+"/_matrix/media/v1/download/", -1) - if info, ok = content["info"].(map[string]interface{}); !ok { - return fmt.Errorf("info isn't a %T", info) - } - if size, ok = info["size"].(float64); !ok { - return fmt.Errorf("size isn't a %T", size) - } - if name, ok = content["body"].(string); !ok { - return fmt.Errorf("name isn't a %T", name) - } - if msgtype, ok = content["msgtype"].(string); !ok { - return fmt.Errorf("msgtype isn't a %T", msgtype) - } - if mtype, ok = info["mimetype"].(string); !ok { - return fmt.Errorf("mtype isn't a %T", mtype) - } + url := strings.ReplaceAll(string(msg.URL), "mxc://", b.GetString("Server")+"/_matrix/media/v1/download/") + filename := msg.Body // check if we have an image uploaded without extension - if !strings.Contains(name, ".") { - if msgtype == "m.image" { - mext, _ := mime.ExtensionsByType(mtype) - if len(mext) > 0 { - name += mext[0] - } + if !strings.Contains(filename, ".") { + mext, _ := mime.ExtensionsByType(msg.Info.MimeType) + if len(mext) > 0 { + filename += mext[0] } else { - // just a default .png extension if we don't have mime info - name += ".png" + if msg.MsgType == event.MsgImage { + // just a default .png extension if we don't have mime info + filename += ".png" + } } } // check if the size is ok - err := helper.HandleDownloadSize(b.Log, rmsg, name, int64(size), b.General) + err := helper.HandleDownloadSize(b.Log, rmsg, filename, int64(msg.Info.Size), b.General) if err != nil { return err } @@ -474,12 +394,12 @@ func (b *Bmatrix) handleDownloadFile(rmsg *config.Message, content map[string]in return fmt.Errorf("download %s failed %#v", url, err) } // add the downloaded data to the message - helper.HandleDownloadData(b.Log, rmsg, name, "", url, data, b.General) + helper.HandleDownloadData(b.Log, rmsg, filename, "", url, data, b.General) return nil } // handleUploadFiles handles native upload of files. -func (b *Bmatrix) handleUploadFiles(msg *config.Message, channel string) (string, error) { +func (b *Bmatrix) handleUploadFiles(msg *config.Message, channel id.RoomID) (string, error) { for _, f := range msg.Extra["file"] { if fi, ok := f.(config.FileInfo); ok { b.handleUploadFile(msg, channel, &fi) @@ -489,17 +409,21 @@ func (b *Bmatrix) handleUploadFiles(msg *config.Message, channel string) (string } // handleUploadFile handles native upload of a file. -func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *config.FileInfo) { +//nolint: funlen +func (b *Bmatrix) handleUploadFile(msg *config.Message, channel id.RoomID, fi *config.FileInfo) { username := newMatrixUsername(msg.Username) content := bytes.NewReader(*fi.Data) sp := strings.Split(fi.Name, ".") mtype := mime.TypeByExtension("." + sp[len(sp)-1]) - // image and video uploads send no username, we have to do this ourself here #715 - err := b.retry(func() error { - _, err := b.mc.SendFormattedText(channel, username.plain+fi.Comment, username.formatted+fi.Comment) - return err - }) + // image and video uploads send no username, we have to do this ourself here #715 + m := event.MessageEventContent{ + MsgType: event.MsgText, + Body: username.plain + fi.Comment, + FormattedBody: username.formatted + fi.Comment, + } + + _, err := b.sendEventWithRetries(channel, event.EventMessage, m) if err != nil { b.Log.Errorf("file comment failed: %#v", err) } @@ -507,9 +431,14 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *conf b.Log.Debugf("uploading file: %s %s", fi.Name, mtype) var res *matrix.RespMediaUpload + req := matrix.ReqUploadMedia{ + Content: content, + ContentType: mtype, + ContentLength: fi.Size, + } err = b.retry(func() error { - res, err = b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data))) + res, err = b.mc.UploadMedia(req) return err }) @@ -519,63 +448,42 @@ func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *conf return } + b.Log.Debugf("result: %#v", res) + + m = event.MessageEventContent{ + Body: fi.Name, + URL: res.ContentURI.CUString(), + } + switch { case strings.Contains(mtype, "video"): b.Log.Debugf("sendVideo %s", res.ContentURI) - err = b.retry(func() error { - _, err = b.mc.SendVideo(channel, fi.Name, res.ContentURI) - return err - }) - if err != nil { - b.Log.Errorf("sendVideo failed: %#v", err) - } + m.MsgType = event.MsgVideo case strings.Contains(mtype, "image"): b.Log.Debugf("sendImage %s", res.ContentURI) - err = b.retry(func() error { - _, err = b.mc.SendImage(channel, fi.Name, res.ContentURI) - return err - }) - if err != nil { - b.Log.Errorf("sendImage failed: %#v", err) - } + m.MsgType = event.MsgImage case strings.Contains(mtype, "audio"): b.Log.Debugf("sendAudio %s", res.ContentURI) - err = b.retry(func() error { - _, err = b.mc.SendMessageEvent(channel, "m.room.message", matrix.AudioMessage{ - MsgType: "m.audio", - Body: fi.Name, - URL: res.ContentURI, - Info: matrix.AudioInfo{ - Mimetype: mtype, - Size: uint(len(*fi.Data)), - }, - }) - return err - }) - if err != nil { - b.Log.Errorf("sendAudio failed: %#v", err) + m.MsgType = event.MsgAudio + m.Info = &event.FileInfo{ + MimeType: mtype, + Size: len(*fi.Data), } default: b.Log.Debugf("sendFile %s", res.ContentURI) - err = b.retry(func() error { - _, err = b.mc.SendMessageEvent(channel, "m.room.message", matrix.FileMessage{ - MsgType: "m.file", - Body: fi.Name, - URL: res.ContentURI, - Info: matrix.FileInfo{ - Mimetype: mtype, - Size: uint(len(*fi.Data)), - }, - }) - return err - }) - if err != nil { - b.Log.Errorf("sendFile failed: %#v", err) + m.MsgType = event.MsgFile + m.Info = &event.FileInfo{ + MimeType: mtype, + Size: len(*fi.Data), } } - b.Log.Debugf("result: %#v", res) + + _, err = b.sendEventWithRetries(channel, event.EventMessage, m) + if err != nil { + b.Log.Errorf("sending the message referencing the uploaded file failed: %#v", err) + } } diff --git a/go.mod b/go.mod index 69914eee..b8b0d372 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/kyokomi/emoji/v2 v2.2.8 github.com/labstack/echo/v4 v4.3.0 github.com/lrstanley/girc v0.0.0-20210611213246-771323f1624b - github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 + github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 // indirect github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20210403163225-761e8622445d github.com/matterbridge/discordgo v0.21.2-0.20210201201054-fb39a175b4f7 github.com/matterbridge/go-xmpp v0.0.0-20200418225040-c8a3a57b4050 @@ -55,6 +55,7 @@ require ( gomod.garykim.dev/nc-talk v0.3.0 gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 layeh.com/gumble v0.0.0-20200818122324-146f9205029b + maunium.net/go/mautrix v0.9.14 ) go 1.15 diff --git a/go.sum b/go.sum index 63f18f9c..6cc1278c 100644 --- a/go.sum +++ b/go.sum @@ -56,7 +56,6 @@ github.com/Azure/azure-sdk-for-go v26.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f h1:2dk3eOnYllh+wUOuDhOoC2vUVoJF/5z478ryJ+wzEII= github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f/go.mod h1:4a58ifQTEe2uwwsaqbh3i2un5/CBPg+At/qHpt18Tmk= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= @@ -94,6 +93,7 @@ github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrU github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/advancedlogic/GoOse v0.0.0-20191112112754-e742535969c1/go.mod h1:f3HCSN1fBWjcpGtXyM119MJgeQl838v6so/PQOqvE1w= github.com/advancedlogic/GoOse v0.0.0-20200830213114-1225d531e0ad/go.mod h1:f3HCSN1fBWjcpGtXyM119MJgeQl838v6so/PQOqvE1w= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -146,6 +146,16 @@ github.com/blevesearch/zap/v14 v14.0.3/go.mod h1:oObAhcDHw7p1ahiTCqhRkdxdl7UA8qp github.com/blevesearch/zap/v15 v15.0.1/go.mod h1:ho0frqAex2ktT9cYFAxQpoQXsxb/KEfdjpx4s49rf/M= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -192,6 +202,7 @@ github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= github.com/d5/tengo/v2 v2.7.0 h1:oAGQ+gcas0/T0bdqvNxfKzjOJhpQRwceWIF5gldB3aM= github.com/d5/tengo/v2 v2.7.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -284,7 +295,6 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-telegram-bot-api/telegram-bot-api v1.0.1-0.20200524105306-7434b0456e81 h1:FdZThbRF0R+2qgyBl3KCVNWWBmKm68E+stT3rnQ02Ww= github.com/go-telegram-bot-api/telegram-bot-api v1.0.1-0.20200524105306-7434b0456e81/go.mod h1:lDm2E64X4OjFdBUA4hlN4mEvbSitvhJdKw7rsA8KHgI= -github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= @@ -348,7 +358,6 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -382,7 +391,6 @@ github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 h1:4EZlYQIiyecYJlUbV github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4/go.mod h1:lEO7XoHJ/xNRBCxrn4h/CEB67h0kW1B0t4ooP2yrjUA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= @@ -468,6 +476,7 @@ github.com/jamiealquiza/envy v1.1.0/go.mod h1:MP36BriGCLwEHhi1OU8E9569JNZrjWfCvz github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -481,6 +490,7 @@ github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUB github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -489,7 +499,6 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= @@ -507,6 +516,7 @@ github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19/go.mod h1:hY+WOq6m2F github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -523,12 +533,10 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE= github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= @@ -544,6 +552,7 @@ github.com/levigross/exp-html v0.0.0-20120902181939-8df60c69a8f5/go.mod h1:QMe2w github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lrstanley/girc v0.0.0-20210611213246-771323f1624b h1:jrLvME7VuLW6NRysbiZtenTB9QcNlR9RPKK4LFfZn60= @@ -556,7 +565,6 @@ github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 h1:ZtO5uywdd5dLDCud4r0r55eP4j9FuUNpl60Gmntcop4= github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s= github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20210403163225-761e8622445d h1:Csy27nDj00vRGp2+QvwSanaW0RA60SZUHXCk4BBT0AU= github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20210403163225-761e8622445d/go.mod h1:c6MxwqHD+0HvtAJjsHMIdPCiAwGiQwPRPTp69ACMg8A= @@ -600,6 +608,7 @@ github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/godown v0.0.1 h1:39uk50ufLVQFs0eapIJVX5fCS74a1Fs2g5f1MVqIHdE= github.com/mattn/godown v0.0.1/go.mod h1:/ivCKurgV/bx6yqtP/Jtc2Xmrv3beCYBvlfAUl4X5g4= @@ -667,7 +676,6 @@ github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9/go.mod h1:A5SRAcpTe github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= @@ -683,13 +691,11 @@ github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= -github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/oov/psd v0.0.0-20201002182931-74231384897f/go.mod h1:GHI1bnmAcbp96z6LNfBJvtrjxhaXGkbsk967utPlvL8= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= @@ -791,6 +797,7 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -843,10 +850,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/slack-go/slack v0.9.1 h1:pekQBs0RmrdAgoqzcMCzUCWSyIkhzUU3F83ExAdZrKo= github.com/slack-go/slack v0.9.1/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= @@ -899,6 +904,10 @@ github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7 github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= github.com/throttled/throttled v2.2.5+incompatible/go.mod h1:0BjlrEGQmvxps+HuXLsyRdqpSRvJpq0PNIsOtqP9Nos= +github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= @@ -944,7 +953,6 @@ github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPy github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw= github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE= -github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= @@ -1010,6 +1018,7 @@ go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1 golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1022,10 +1031,12 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1124,6 +1135,7 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= @@ -1229,6 +1241,7 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1315,10 +1328,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomod.garykim.dev/nc-talk v0.2.2 h1:+U+daJFPPuwM7yRXYazeMHZgIBSGP6SeQURO0O5a32I= -gomod.garykim.dev/nc-talk v0.2.2/go.mod h1:q/Adot/H7iqi+H4lANopV7/xcMf+sX3AZXUXqiITwok= gomod.garykim.dev/nc-talk v0.3.0 h1:MZxLc/gX2/+bdOw4xt6pi+qQFUQld1woGfw1hEJ0fbM= gomod.garykim.dev/nc-talk v0.3.0/go.mod h1:q/Adot/H7iqi+H4lANopV7/xcMf+sX3AZXUXqiITwok= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= @@ -1456,7 +1466,6 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -1477,7 +1486,6 @@ gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 h1:sY2a+y0j4iDrajJc gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376/go.mod h1:BHKOc1m5wm8WwQkMqYBoo4vNxhmF7xg8+xhG8L+Cy3M= gopkg.in/olivere/elastic.v6 v6.2.35/go.mod h1:2cTT8Z+/LcArSWpCgvZqBgt3VOqXiy7v00w12Lz8bd4= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= @@ -1507,6 +1515,9 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 layeh.com/gopus v0.0.0-20161224163843-0ebf989153aa/go.mod h1:AOef7vHz0+v4sWwJnr0jSyHiX/1NgsMoaxl+rEPz/I0= layeh.com/gumble v0.0.0-20200818122324-146f9205029b h1:Kne6wkHqbqrygRsqs5XUNhSs84DFG5TYMeCkCbM56sY= layeh.com/gumble v0.0.0-20200818122324-146f9205029b/go.mod h1:tWPVA9ZAfImNwabjcd9uDE+Mtz0Hfs7a7G3vxrnrwyc= +maunium.net/go/maulogger/v2 v2.2.4/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A= +maunium.net/go/mautrix v0.9.14 h1:2MMJ630VM+xfa4Q5AooMAhPG1+wQnQybSr/z8PlRZ8A= +maunium.net/go/mautrix v0.9.14/go.mod h1:7IzKfWvpQtN+W2Lzxc0rLvIxFM3ryKX6Ys3S/ZoWbg8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/vendor/github.com/btcsuite/btcutil/LICENSE b/vendor/github.com/btcsuite/btcutil/LICENSE new file mode 100644 index 00000000..3e7b1679 --- /dev/null +++ b/vendor/github.com/btcsuite/btcutil/LICENSE @@ -0,0 +1,16 @@ +ISC License + +Copyright (c) 2013-2017 The btcsuite developers +Copyright (c) 2016-2017 The Lightning Network Developers + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/vendor/github.com/btcsuite/btcutil/base58/README.md b/vendor/github.com/btcsuite/btcutil/base58/README.md new file mode 100644 index 00000000..98dfb1db --- /dev/null +++ b/vendor/github.com/btcsuite/btcutil/base58/README.md @@ -0,0 +1,34 @@ +base58 +========== + +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)](https://travis-ci.org/btcsuite/btcutil) +[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/btcsuite/btcutil/base58) + +Package base58 provides an API for encoding and decoding to and from the +modified base58 encoding. It also provides an API to do Base58Check encoding, +as described [here](https://en.bitcoin.it/wiki/Base58Check_encoding). + +A comprehensive suite of tests is provided to ensure proper functionality. + +## Installation and Updating + +```bash +$ go get -u github.com/btcsuite/btcutil/base58 +``` + +## Examples + +* [Decode Example](http://godoc.org/github.com/btcsuite/btcutil/base58#example-Decode) + Demonstrates how to decode modified base58 encoded data. +* [Encode Example](http://godoc.org/github.com/btcsuite/btcutil/base58#example-Encode) + Demonstrates how to encode data using the modified base58 encoding scheme. +* [CheckDecode Example](http://godoc.org/github.com/btcsuite/btcutil/base58#example-CheckDecode) + Demonstrates how to decode Base58Check encoded data. +* [CheckEncode Example](http://godoc.org/github.com/btcsuite/btcutil/base58#example-CheckEncode) + Demonstrates how to encode data using the Base58Check encoding scheme. + +## License + +Package base58 is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/vendor/github.com/btcsuite/btcutil/base58/alphabet.go b/vendor/github.com/btcsuite/btcutil/base58/alphabet.go new file mode 100644 index 00000000..6bb39fef --- /dev/null +++ b/vendor/github.com/btcsuite/btcutil/base58/alphabet.go @@ -0,0 +1,49 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +// AUTOGENERATED by genalphabet.go; do not edit. + +package base58 + +const ( + // alphabet is the modified base58 alphabet used by Bitcoin. + alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + + alphabetIdx0 = '1' +) + +var b58 = [256]byte{ + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 255, 255, 255, 255, 255, 255, + 255, 9, 10, 11, 12, 13, 14, 15, + 16, 255, 17, 18, 19, 20, 21, 255, + 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 255, 255, 255, 255, 255, + 255, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 255, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, +} diff --git a/vendor/github.com/btcsuite/btcutil/base58/base58.go b/vendor/github.com/btcsuite/btcutil/base58/base58.go new file mode 100644 index 00000000..19a72de2 --- /dev/null +++ b/vendor/github.com/btcsuite/btcutil/base58/base58.go @@ -0,0 +1,75 @@ +// Copyright (c) 2013-2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package base58 + +import ( + "math/big" +) + +//go:generate go run genalphabet.go + +var bigRadix = big.NewInt(58) +var bigZero = big.NewInt(0) + +// Decode decodes a modified base58 string to a byte slice. +func Decode(b string) []byte { + answer := big.NewInt(0) + j := big.NewInt(1) + + scratch := new(big.Int) + for i := len(b) - 1; i >= 0; i-- { + tmp := b58[b[i]] + if tmp == 255 { + return []byte("") + } + scratch.SetInt64(int64(tmp)) + scratch.Mul(j, scratch) + answer.Add(answer, scratch) + j.Mul(j, bigRadix) + } + + tmpval := answer.Bytes() + + var numZeros int + for numZeros = 0; numZeros < len(b); numZeros++ { + if b[numZeros] != alphabetIdx0 { + break + } + } + flen := numZeros + len(tmpval) + val := make([]byte, flen) + copy(val[numZeros:], tmpval) + + return val +} + +// Encode encodes a byte slice to a modified base58 string. +func Encode(b []byte) string { + x := new(big.Int) + x.SetBytes(b) + + answer := make([]byte, 0, len(b)*136/100) + for x.Cmp(bigZero) > 0 { + mod := new(big.Int) + x.DivMod(x, bigRadix, mod) + answer = append(answer, alphabet[mod.Int64()]) + } + + // leading zero bytes + for _, i := range b { + if i != 0 { + break + } + answer = append(answer, alphabetIdx0) + } + + // reverse + alen := len(answer) + for i := 0; i < alen/2; i++ { + answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i] + } + + return string(answer) +} diff --git a/vendor/github.com/btcsuite/btcutil/base58/base58check.go b/vendor/github.com/btcsuite/btcutil/base58/base58check.go new file mode 100644 index 00000000..7cdafeee --- /dev/null +++ b/vendor/github.com/btcsuite/btcutil/base58/base58check.go @@ -0,0 +1,52 @@ +// Copyright (c) 2013-2014 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package base58 + +import ( + "crypto/sha256" + "errors" +) + +// ErrChecksum indicates that the checksum of a check-encoded string does not verify against +// the checksum. +var ErrChecksum = errors.New("checksum error") + +// ErrInvalidFormat indicates that the check-encoded string has an invalid format. +var ErrInvalidFormat = errors.New("invalid format: version and/or checksum bytes missing") + +// checksum: first four bytes of sha256^2 +func checksum(input []byte) (cksum [4]byte) { + h := sha256.Sum256(input) + h2 := sha256.Sum256(h[:]) + copy(cksum[:], h2[:4]) + return +} + +// CheckEncode prepends a version byte and appends a four byte checksum. +func CheckEncode(input []byte, version byte) string { + b := make([]byte, 0, 1+len(input)+4) + b = append(b, version) + b = append(b, input[:]...) + cksum := checksum(b) + b = append(b, cksum[:]...) + return Encode(b) +} + +// CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum. +func CheckDecode(input string) (result []byte, version byte, err error) { + decoded := Decode(input) + if len(decoded) < 5 { + return nil, 0, ErrInvalidFormat + } + version = decoded[0] + var cksum [4]byte + copy(cksum[:], decoded[len(decoded)-4:]) + if checksum(decoded[:len(decoded)-4]) != cksum { + return nil, 0, ErrChecksum + } + payload := decoded[1 : len(decoded)-4] + result = append(result, payload...) + return +} diff --git a/vendor/github.com/btcsuite/btcutil/base58/cov_report.sh b/vendor/github.com/btcsuite/btcutil/base58/cov_report.sh new file mode 100644 index 00000000..307f05b7 --- /dev/null +++ b/vendor/github.com/btcsuite/btcutil/base58/cov_report.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# This script uses gocov to generate a test coverage report. +# The gocov tool my be obtained with the following command: +# go get github.com/axw/gocov/gocov +# +# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH. + +# Check for gocov. +type gocov >/dev/null 2>&1 +if [ $? -ne 0 ]; then + echo >&2 "This script requires the gocov tool." + echo >&2 "You may obtain it with the following command:" + echo >&2 "go get github.com/axw/gocov/gocov" + exit 1 +fi +gocov test | gocov report diff --git a/vendor/github.com/btcsuite/btcutil/base58/doc.go b/vendor/github.com/btcsuite/btcutil/base58/doc.go new file mode 100644 index 00000000..9a2c0e6e --- /dev/null +++ b/vendor/github.com/btcsuite/btcutil/base58/doc.go @@ -0,0 +1,29 @@ +// Copyright (c) 2014 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +/* +Package base58 provides an API for working with modified base58 and Base58Check +encodings. + +Modified Base58 Encoding + +Standard base58 encoding is similar to standard base64 encoding except, as the +name implies, it uses a 58 character alphabet which results in an alphanumeric +string and allows some characters which are problematic for humans to be +excluded. Due to this, there can be various base58 alphabets. + +The modified base58 alphabet used by Bitcoin, and hence this package, omits the +0, O, I, and l characters that look the same in many fonts and are therefore +hard to humans to distinguish. + +Base58Check Encoding Scheme + +The Base58Check encoding scheme is primarily used for Bitcoin addresses at the +time of this writing, however it can be used to generically encode arbitrary +byte arrays into human-readable strings along with a version byte that can be +used to differentiate the same payload. For Bitcoin addresses, the extra +version is used to differentiate the network of otherwise identical public keys +which helps prevent using an address intended for one network on another. +*/ +package base58 diff --git a/vendor/github.com/matrix-org/gomatrix/.gitignore b/vendor/github.com/matrix-org/gomatrix/.gitignore deleted file mode 100644 index 0dd56286..00000000 --- a/vendor/github.com/matrix-org/gomatrix/.gitignore +++ /dev/null @@ -1,28 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so -*.out - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof - -# test editor files -*.swp diff --git a/vendor/github.com/matrix-org/gomatrix/.golangci.yml b/vendor/github.com/matrix-org/gomatrix/.golangci.yml deleted file mode 100644 index 15eb6ef7..00000000 --- a/vendor/github.com/matrix-org/gomatrix/.golangci.yml +++ /dev/null @@ -1,21 +0,0 @@ -run: - timeout: 5m - linters: - enable: - - vet - - vetshadow - - typecheck - - deadcode - - gocyclo - - golint - - varcheck - - structcheck - - maligned - - ineffassign - - misspell - - unparam - - goimports - - goconst - - unconvert - - errcheck - - interfacer diff --git a/vendor/github.com/matrix-org/gomatrix/.travis.yml b/vendor/github.com/matrix-org/gomatrix/.travis.yml deleted file mode 100644 index 46997ab6..00000000 --- a/vendor/github.com/matrix-org/gomatrix/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: go -go: - - 1.13.10 -install: - - go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.24.0 - - go build -script: ./hooks/pre-commit diff --git a/vendor/github.com/matrix-org/gomatrix/CHANGELOG.md b/vendor/github.com/matrix-org/gomatrix/CHANGELOG.md deleted file mode 100644 index b6a9a167..00000000 --- a/vendor/github.com/matrix-org/gomatrix/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -## Release 0.1.0 (UNRELEASED) diff --git a/vendor/github.com/matrix-org/gomatrix/LICENSE b/vendor/github.com/matrix-org/gomatrix/LICENSE deleted file mode 100644 index 8dada3ed..00000000 --- a/vendor/github.com/matrix-org/gomatrix/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/matrix-org/gomatrix/README.md b/vendor/github.com/matrix-org/gomatrix/README.md deleted file mode 100644 index a083b46c..00000000 --- a/vendor/github.com/matrix-org/gomatrix/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# gomatrix -[![GoDoc](https://godoc.org/github.com/matrix-org/gomatrix?status.svg)](https://godoc.org/github.com/matrix-org/gomatrix) - -A Golang Matrix client. - -**THIS IS UNDER ACTIVE DEVELOPMENT: BREAKING CHANGES ARE FREQUENT.** - -# Contributing - -All contributions are greatly appreciated! - -## How to report issues - -Please check the current open issues for similar reports -in order to avoid duplicates. - -Some general guidelines: - -- Include a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) when possible. -- Describe the expected behaviour and what actually happened - including a full trace-back in case of exceptions. -- Make sure to list details about your environment - -## Setting up your environment - -If you intend to contribute to gomatrix you'll first need Go installed on your machine (version 1.12+ is required). Also, make sure to have golangci-lint properly set up since we use it for pre-commit hooks (for instructions on how to install it, check the [official docs](https://golangci-lint.run/usage/install/#local-installation)). - -- Fork gomatrix to your GitHub account by clicking the [Fork](https://github.com/matrix-org/gomatrix/fork) button. -- [Clone](https://help.github.com/en/articles/fork-a-repo#step-2-create-a-local-clone-of-your-fork) the main repository (not your fork) to your local machine. - - - $ git clone https://github.com/matrix-org/gomatrix - $ cd gomatrix - - -- Add your fork as a remote to push your contributions.Replace - ``{username}`` with your username. - - git remote add fork https://github.com/{username}/gomatrix - -- Create a new branch to identify what feature you are working on. - - $ git fetch origin - $ git checkout -b your-branch-name origin/master - - -- Make your changes, including tests that cover any code changes you make, and run them as described below. - -- Execute pre-commit hooks by running - - /hooks/pre-commit - -- Push your changes to your fork and [create a pull request](https://help.github.com/en/articles/creating-a-pull-request) describing your changes. - - $ git push --set-upstream fork your-branch-name - -- Finally, create a [pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) - -## How to run tests - -You can run the test suite and example code with `$ go test -v` - -# Running Coverage - -To run coverage, first generate the coverage report using `go test` - - go test -v -cover -coverprofile=coverage.out - -You can now show the generated report as a html page with `go tool` - - go tool cover -html=coverage.out diff --git a/vendor/github.com/matrix-org/gomatrix/client.go b/vendor/github.com/matrix-org/gomatrix/client.go deleted file mode 100644 index d0bb0c5e..00000000 --- a/vendor/github.com/matrix-org/gomatrix/client.go +++ /dev/null @@ -1,805 +0,0 @@ -// Package gomatrix implements the Matrix Client-Server API. -// -// Specification can be found at http://matrix.org/docs/spec/client_server/r0.2.0.html -package gomatrix - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "path" - "strconv" - "strings" - "sync" - "time" -) - -// Client represents a Matrix client. -type Client struct { - HomeserverURL *url.URL // The base homeserver URL - Prefix string // The API prefix eg '/_matrix/client/r0' - UserID string // The user ID of the client. Used for forming HTTP paths which use the client's user ID. - AccessToken string // The access_token for the client. - 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 - - // 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. - // See http://matrix.org/docs/spec/application_service/unstable.html#identity-assertion - AppServiceUserID string - - syncingMutex sync.Mutex // protects syncingID - syncingID uint32 // Identifies the current Sync. Only one Sync can be active at any given time. -} - -// HTTPError An HTTP Error response, which may wrap an underlying native Go Error. -type HTTPError struct { - Contents []byte - WrappedError error - Message string - Code int -} - -func (e HTTPError) Error() string { - var wrappedErrMsg string - if e.WrappedError != nil { - wrappedErrMsg = e.WrappedError.Error() - } - return fmt.Sprintf("contents=%v msg=%s code=%d wrapped=%s", e.Contents, e.Message, e.Code, wrappedErrMsg) -} - -// BuildURL builds a URL with the Client's homeserver/prefix set already. -func (cli *Client) BuildURL(urlPath ...string) string { - ps := append([]string{cli.Prefix}, urlPath...) - return cli.BuildBaseURL(ps...) -} - -// BuildBaseURL builds a URL with the Client's homeserver set already. You must -// supply the prefix in the path. -func (cli *Client) BuildBaseURL(urlPath ...string) string { - // copy the URL. Purposefully ignore error as the input is from a valid URL already - hsURL, _ := url.Parse(cli.HomeserverURL.String()) - parts := []string{hsURL.Path} - parts = append(parts, urlPath...) - hsURL.Path = path.Join(parts...) - // Manually add the trailing slash back to the end of the path if it's explicitly needed - if strings.HasSuffix(urlPath[len(urlPath)-1], "/") { - hsURL.Path = hsURL.Path + "/" - } - query := hsURL.Query() - if cli.AppServiceUserID != "" { - query.Set("user_id", cli.AppServiceUserID) - } - hsURL.RawQuery = query.Encode() - return hsURL.String() -} - -// BuildURLWithQuery builds a URL with query parameters in addition to the Client's homeserver/prefix set already. -func (cli *Client) BuildURLWithQuery(urlPath []string, urlQuery map[string]string) string { - u, _ := url.Parse(cli.BuildURL(urlPath...)) - q := u.Query() - for k, v := range urlQuery { - q.Set(k, v) - } - u.RawQuery = q.Encode() - return u.String() -} - -// SetCredentials sets the user ID and access token on this client instance. -func (cli *Client) SetCredentials(userID, accessToken string) { - cli.AccessToken = accessToken - cli.UserID = userID -} - -// ClearCredentials removes the user ID and access token on this client instance. -func (cli *Client) ClearCredentials() { - cli.AccessToken = "" - cli.UserID = "" -} - -// Sync starts syncing with the provided Homeserver. If Sync() is called twice then the first sync will be stopped and the -// error will be nil. -// -// This function will block until a fatal /sync error occurs, so it should almost always be started as a new goroutine. -// Fatal sync errors can be caused by: -// - The failure to create a filter. -// - Client.Syncer.OnFailedSync returning an error in response to a failed sync. -// - Client.Syncer.ProcessResponse returning an error. -// If you wish to continue retrying in spite of these fatal errors, call Sync() again. -func (cli *Client) Sync() error { - // Mark the client as syncing. - // We will keep syncing until the syncing state changes. Either because - // Sync is called or StopSync is called. - syncingID := cli.incrementSyncingID() - nextBatch := cli.Store.LoadNextBatch(cli.UserID) - filterID := cli.Store.LoadFilterID(cli.UserID) - if filterID == "" { - filterJSON := cli.Syncer.GetFilterJSON(cli.UserID) - resFilter, err := cli.CreateFilter(filterJSON) - if err != nil { - return err - } - filterID = resFilter.FilterID - cli.Store.SaveFilterID(cli.UserID, filterID) - } - - for { - resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, "") - if err != nil { - duration, err2 := cli.Syncer.OnFailedSync(resSync, err) - if err2 != nil { - return err2 - } - time.Sleep(duration) - continue - } - - // Check that the syncing state hasn't changed - // Either because we've stopped syncing or another sync has been started. - // We discard the response from our sync. - if cli.getSyncingID() != syncingID { - return nil - } - - // Save the token now *before* processing it. This means it's possible - // to not process some events, but it means that we won't get constantly stuck processing - // a malformed/buggy event which keeps making us panic. - cli.Store.SaveNextBatch(cli.UserID, resSync.NextBatch) - if err = cli.Syncer.ProcessResponse(resSync, nextBatch); err != nil { - return err - } - - nextBatch = resSync.NextBatch - } -} - -func (cli *Client) incrementSyncingID() uint32 { - cli.syncingMutex.Lock() - defer cli.syncingMutex.Unlock() - cli.syncingID++ - return cli.syncingID -} - -func (cli *Client) getSyncingID() uint32 { - cli.syncingMutex.Lock() - defer cli.syncingMutex.Unlock() - return cli.syncingID -} - -// StopSync stops the ongoing sync started by Sync. -func (cli *Client) StopSync() { - // Advance the syncing state so that any running Syncs will terminate. - cli.incrementSyncingID() -} - -// MakeRequest makes a JSON HTTP request to the given URL. -// The response body will be stream decoded into an interface. This will automatically stop if the response -// body is nil. -// -// Returns an error if the response is not 2xx along with the HTTP body bytes if it got that far. This error is -// an HTTPError which includes the returned HTTP status code, byte contents of the response body and possibly a -// RespError as the WrappedError, if the HTTP body could be decoded as a RespError. -func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) error { - var req *http.Request - var err error - if reqBody != nil { - buf := new(bytes.Buffer) - if err := json.NewEncoder(buf).Encode(reqBody); err != nil { - return err - } - req, err = http.NewRequest(method, httpURL, buf) - } else { - req, err = http.NewRequest(method, httpURL, nil) - } - - if err != nil { - return err - } - - req.Header.Set("Content-Type", "application/json") - - if cli.AccessToken != "" { - req.Header.Set("Authorization", "Bearer "+cli.AccessToken) - } - - res, err := cli.Client.Do(req) - if res != nil { - defer res.Body.Close() - } - if err != nil { - return err - } - if res.StatusCode/100 != 2 { // not 2xx - contents, err := ioutil.ReadAll(res.Body) - if err != nil { - return err - } - - var wrap error - var respErr RespError - if _ = json.Unmarshal(contents, &respErr); respErr.ErrCode != "" { - wrap = respErr - } - - // If we failed to decode as RespError, don't just drop the HTTP body, include it in the - // HTTP error instead (e.g proxy errors which return HTML). - msg := "Failed to " + method + " JSON to " + req.URL.Path - if wrap == nil { - msg = msg + ": " + string(contents) - } - - return HTTPError{ - Contents: contents, - Code: res.StatusCode, - Message: msg, - WrappedError: wrap, - } - } - - if resBody != nil && res.Body != nil { - return json.NewDecoder(res.Body).Decode(&resBody) - } - - return nil -} - -// CreateFilter makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter -func (cli *Client) CreateFilter(filter json.RawMessage) (resp *RespCreateFilter, err error) { - urlPath := cli.BuildURL("user", cli.UserID, "filter") - err = cli.MakeRequest("POST", urlPath, &filter, &resp) - return -} - -// SyncRequest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync -func (cli *Client) SyncRequest(timeout int, since, filterID string, fullState bool, setPresence string) (resp *RespSync, err error) { - query := map[string]string{ - "timeout": strconv.Itoa(timeout), - } - if since != "" { - query["since"] = since - } - if filterID != "" { - query["filter"] = filterID - } - if setPresence != "" { - query["set_presence"] = setPresence - } - if fullState { - query["full_state"] = "true" - } - urlPath := cli.BuildURLWithQuery([]string{"sync"}, query) - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -func (cli *Client) register(u string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) { - err = cli.MakeRequest("POST", u, req, &resp) - if err != nil { - httpErr, ok := err.(HTTPError) - if !ok { // network error - return - } - if httpErr.Code == 401 { - // body should be RespUserInteractive, if it isn't, fail with the error - err = json.Unmarshal(httpErr.Contents, &uiaResp) - return - } - } - return -} - -// Register makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register -// -// Registers with kind=user. For kind=guest, see RegisterGuest. -func (cli *Client) Register(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) { - u := cli.BuildURL("register") - return cli.register(u, req) -} - -// RegisterGuest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register -// with kind=guest. -// -// For kind=user, see Register. -func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) { - query := map[string]string{ - "kind": "guest", - } - u := cli.BuildURLWithQuery([]string{"register"}, query) - return cli.register(u, req) -} - -// RegisterDummy performs m.login.dummy registration according to https://matrix.org/docs/spec/client_server/r0.2.0.html#dummy-auth -// -// Only a username and password need to be provided on the ReqRegister struct. Most local/developer homeservers will allow registration -// this way. If the homeserver does not, an error is returned. -// -// This does not set credentials on the client instance. See SetCredentials() instead. -// -// res, err := cli.RegisterDummy(&gomatrix.ReqRegister{ -// Username: "alice", -// Password: "wonderland", -// }) -// if err != nil { -// panic(err) -// } -// token := res.AccessToken -func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) { - res, uia, err := cli.Register(req) - if err != nil && uia == nil { - return nil, err - } - if uia != nil && uia.HasSingleStageFlow("m.login.dummy") { - req.Auth = struct { - Type string `json:"type"` - Session string `json:"session,omitempty"` - }{"m.login.dummy", uia.Session} - res, _, err = cli.Register(req) - if err != nil { - return nil, err - } - } - if res == nil { - return nil, fmt.Errorf("registration failed: does this server support m.login.dummy?") - } - return res, nil -} - -// Login a user to the homeserver according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login -// This does not set credentials on this client instance. See SetCredentials() instead. -func (cli *Client) Login(req *ReqLogin) (resp *RespLogin, err error) { - urlPath := cli.BuildURL("login") - err = cli.MakeRequest("POST", urlPath, req, &resp) - return -} - -// Logout the current user. See http://matrix.org/docs/spec/client_server/r0.6.0.html#post-matrix-client-r0-logout -// This does not clear the credentials from the client instance. See ClearCredentials() instead. -func (cli *Client) Logout() (resp *RespLogout, err error) { - urlPath := cli.BuildURL("logout") - err = cli.MakeRequest("POST", urlPath, nil, &resp) - return -} - -// LogoutAll logs the current user out on all devices. See https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-logout-all -// This does not clear the credentials from the client instance. See ClearCredentails() instead. -func (cli *Client) LogoutAll() (resp *RespLogoutAll, err error) { - urlPath := cli.BuildURL("logout/all") - err = cli.MakeRequest("POST", urlPath, nil, &resp) - return -} - -// Versions returns the list of supported Matrix versions on this homeserver. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions -func (cli *Client) Versions() (resp *RespVersions, err error) { - urlPath := cli.BuildBaseURL("_matrix", "client", "versions") - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -// PublicRooms returns the list of public rooms on target server. See https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-unstable-publicrooms -func (cli *Client) PublicRooms(limit int, since string, server string) (resp *RespPublicRooms, err error) { - args := map[string]string{} - - if limit != 0 { - args["limit"] = strconv.Itoa(limit) - } - if since != "" { - args["since"] = since - } - if server != "" { - args["server"] = server - } - - urlPath := cli.BuildURLWithQuery([]string{"publicRooms"}, args) - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -// PublicRoomsFiltered returns a subset of PublicRooms filtered server side. -// See https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-unstable-publicrooms -func (cli *Client) PublicRoomsFiltered(limit int, since string, server string, filter string) (resp *RespPublicRooms, err error) { - content := map[string]string{} - - if limit != 0 { - content["limit"] = strconv.Itoa(limit) - } - if since != "" { - content["since"] = since - } - if filter != "" { - content["filter"] = filter - } - - var urlPath string - if server == "" { - urlPath = cli.BuildURL("publicRooms") - } else { - urlPath = cli.BuildURLWithQuery([]string{"publicRooms"}, map[string]string{ - "server": server, - }) - } - - err = cli.MakeRequest("POST", urlPath, content, &resp) - return -} - -// JoinRoom joins the client to a room ID or alias. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-join-roomidoralias -// -// If serverName is specified, this will be added as a query param to instruct the homeserver to join via that server. If content is specified, it will -// be JSON encoded and used as the request body. -func (cli *Client) JoinRoom(roomIDorAlias, serverName string, content interface{}) (resp *RespJoinRoom, err error) { - var urlPath string - if serverName != "" { - urlPath = cli.BuildURLWithQuery([]string{"join", roomIDorAlias}, map[string]string{ - "server_name": serverName, - }) - } else { - urlPath = cli.BuildURL("join", roomIDorAlias) - } - err = cli.MakeRequest("POST", urlPath, content, &resp) - return -} - -// GetDisplayName returns the display name of the user from the specified MXID. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname -func (cli *Client) GetDisplayName(mxid string) (resp *RespUserDisplayName, err error) { - urlPath := cli.BuildURL("profile", mxid, "displayname") - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -// GetOwnDisplayName returns the user's display name. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname -func (cli *Client) GetOwnDisplayName() (resp *RespUserDisplayName, err error) { - urlPath := cli.BuildURL("profile", cli.UserID, "displayname") - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -// SetDisplayName sets the user's profile display name. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-displayname -func (cli *Client) SetDisplayName(displayName string) (err error) { - urlPath := cli.BuildURL("profile", cli.UserID, "displayname") - s := struct { - DisplayName string `json:"displayname"` - }{displayName} - err = cli.MakeRequest("PUT", urlPath, &s, nil) - return -} - -// GetAvatarURL gets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-avatar-url -func (cli *Client) GetAvatarURL() (string, error) { - urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url") - s := struct { - AvatarURL string `json:"avatar_url"` - }{} - - err := cli.MakeRequest("GET", urlPath, nil, &s) - if err != nil { - return "", err - } - - return s.AvatarURL, nil -} - -// SetAvatarURL sets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-avatar-url -func (cli *Client) SetAvatarURL(url string) error { - urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url") - s := struct { - AvatarURL string `json:"avatar_url"` - }{url} - err := cli.MakeRequest("PUT", urlPath, &s, nil) - if err != nil { - return err - } - - return nil -} - -// GetStatus returns the status of the user from the specified MXID. See https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-presence-userid-status -func (cli *Client) GetStatus(mxid string) (resp *RespUserStatus, err error) { - urlPath := cli.BuildURL("presence", mxid, "status") - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -// GetOwnStatus returns the user's status. See https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-presence-userid-status -func (cli *Client) GetOwnStatus() (resp *RespUserStatus, err error) { - return cli.GetStatus(cli.UserID) -} - -// SetStatus sets the user's status. See https://matrix.org/docs/spec/client_server/r0.6.0#put-matrix-client-r0-presence-userid-status -func (cli *Client) SetStatus(presence, status string) (err error) { - urlPath := cli.BuildURL("presence", cli.UserID, "status") - s := struct { - Presence string `json:"presence"` - StatusMsg string `json:"status_msg"` - }{presence, status} - err = cli.MakeRequest("PUT", urlPath, &s, nil) - 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) SendMessageEvent(roomID string, eventType string, contentJSON interface{}) (resp *RespSendEvent, err error) { - txnID := txnID() - urlPath := cli.BuildURL("rooms", roomID, "send", eventType, txnID) - 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) - err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp) - return -} - -// 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{MsgType: "m.text", Body: text}) -} - -// SendFormattedText sends an m.room.message event into the given room with a msgtype of m.text, supports a subset of HTML for formatting. -// See https://matrix.org/docs/spec/client_server/r0.6.0#m-text -func (cli *Client) SendFormattedText(roomID, text, formattedText string) (*RespSendEvent, error) { - return cli.SendMessageEvent(roomID, "m.room.message", - TextMessage{MsgType: "m.text", Body: text, FormattedBody: formattedText, Format: "org.matrix.custom.html"}) -} - -// 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, - }) -} - -// 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, - }) -} - -// 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{MsgType: "m.notice", 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 -func (cli *Client) RedactEvent(roomID, eventID string, req *ReqRedact) (resp *RespSendEvent, err error) { - txnID := txnID() - urlPath := cli.BuildURL("rooms", roomID, "redact", eventID, txnID) - err = cli.MakeRequest("PUT", urlPath, req, &resp) - return -} - -// MarkRead marks eventID in roomID as read, signifying the event, and all before it have been read. See https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-rooms-roomid-receipt-receipttype-eventid -func (cli *Client) MarkRead(roomID, eventID string) error { - urlPath := cli.BuildURL("rooms", roomID, "receipt", "m.read", eventID) - return cli.MakeRequest("POST", urlPath, nil, nil) -} - -// CreateRoom creates a new Matrix room. See https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom -// resp, err := cli.CreateRoom(&gomatrix.ReqCreateRoom{ -// Preset: "public_chat", -// }) -// fmt.Println("Room:", resp.RoomID) -func (cli *Client) CreateRoom(req *ReqCreateRoom) (resp *RespCreateRoom, err error) { - urlPath := cli.BuildURL("createRoom") - err = cli.MakeRequest("POST", urlPath, req, &resp) - return -} - -// LeaveRoom leaves the given room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave -func (cli *Client) LeaveRoom(roomID string) (resp *RespLeaveRoom, err error) { - u := cli.BuildURL("rooms", roomID, "leave") - err = cli.MakeRequest("POST", u, struct{}{}, &resp) - return -} - -// ForgetRoom forgets a room entirely. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget -func (cli *Client) ForgetRoom(roomID string) (resp *RespForgetRoom, err error) { - u := cli.BuildURL("rooms", roomID, "forget") - err = cli.MakeRequest("POST", u, struct{}{}, &resp) - return -} - -// InviteUser invites a user to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite -func (cli *Client) InviteUser(roomID string, req *ReqInviteUser) (resp *RespInviteUser, err error) { - u := cli.BuildURL("rooms", roomID, "invite") - err = cli.MakeRequest("POST", u, req, &resp) - return -} - -// InviteUserByThirdParty invites a third-party identifier to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#invite-by-third-party-id-endpoint -func (cli *Client) InviteUserByThirdParty(roomID string, req *ReqInvite3PID) (resp *RespInviteUser, err error) { - u := cli.BuildURL("rooms", roomID, "invite") - err = cli.MakeRequest("POST", u, req, &resp) - return -} - -// KickUser kicks a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick -func (cli *Client) KickUser(roomID string, req *ReqKickUser) (resp *RespKickUser, err error) { - u := cli.BuildURL("rooms", roomID, "kick") - err = cli.MakeRequest("POST", u, req, &resp) - return -} - -// BanUser bans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban -func (cli *Client) BanUser(roomID string, req *ReqBanUser) (resp *RespBanUser, err error) { - u := cli.BuildURL("rooms", roomID, "ban") - err = cli.MakeRequest("POST", u, req, &resp) - return -} - -// UnbanUser unbans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban -func (cli *Client) UnbanUser(roomID string, req *ReqUnbanUser) (resp *RespUnbanUser, err error) { - u := cli.BuildURL("rooms", roomID, "unban") - err = cli.MakeRequest("POST", u, req, &resp) - return -} - -// UserTyping sets the typing status of the user. See https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid -func (cli *Client) UserTyping(roomID string, typing bool, timeout int64) (resp *RespTyping, err error) { - req := ReqTyping{Typing: typing, Timeout: timeout} - u := cli.BuildURL("rooms", roomID, "typing", cli.UserID) - err = cli.MakeRequest("PUT", u, req, &resp) - 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) - err = cli.MakeRequest("GET", u, nil, outContent) - return -} - -// UploadLink uploads an HTTP URL and then returns an MXC URI. -func (cli *Client) UploadLink(link string) (*RespMediaUpload, error) { - res, err := cli.Client.Get(link) - if res != nil { - defer res.Body.Close() - } - if err != nil { - return nil, err - } - return cli.UploadToContentRepo(res.Body, res.Header.Get("Content-Type"), res.ContentLength) -} - -// 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) { - 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.Header.Set("Authorization", "Bearer "+cli.AccessToken) - - req.ContentLength = contentLength - - res, err := cli.Client.Do(req) - if res != nil { - defer res.Body.Close() - } - - if err != nil { - return nil, err - } - - if res.StatusCode != 200 { - contents, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, HTTPError{ - Message: "Upload request failed - Failed to read response body: " + err.Error(), - Code: res.StatusCode, - } - } - return nil, HTTPError{ - Contents: contents, - Message: "Upload request failed: " + string(contents), - Code: res.StatusCode, - } - } - - var m RespMediaUpload - if err := json.NewDecoder(res.Body).Decode(&m); err != nil { - return nil, err - } - - return &m, nil -} - -// JoinedMembers returns a map of joined room members. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680 -// -// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes. -// This API is primarily designed for application services which may want to efficiently look up joined members in a room. -func (cli *Client) JoinedMembers(roomID string) (resp *RespJoinedMembers, err error) { - u := cli.BuildURL("rooms", roomID, "joined_members") - err = cli.MakeRequest("GET", u, nil, &resp) - return -} - -// JoinedRooms returns a list of rooms which the client is joined to. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680 -// -// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes. -// This API is primarily designed for application services which may want to efficiently look up joined rooms. -func (cli *Client) JoinedRooms() (resp *RespJoinedRooms, err error) { - u := cli.BuildURL("joined_rooms") - err = cli.MakeRequest("GET", u, nil, &resp) - return -} - -// Messages returns a list of message and state events for a room. It uses -// pagination query parameters to paginate history in the room. -// See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages -func (cli *Client) Messages(roomID, from, to string, dir rune, limit int) (resp *RespMessages, err error) { - query := map[string]string{ - "from": from, - "dir": string(dir), - } - if to != "" { - query["to"] = to - } - if limit != 0 { - query["limit"] = strconv.Itoa(limit) - } - - urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "messages"}, query) - err = cli.MakeRequest("GET", urlPath, nil, &resp) - 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) { - urlPath := cli.BuildURL("voip", "turnServer") - err = cli.MakeRequest("GET", urlPath, nil, &resp) - return -} - -func txnID() string { - return "go" + strconv.FormatInt(time.Now().UnixNano(), 10) -} - -// NewClient creates a new Matrix Client ready for syncing -func NewClient(homeserverURL, userID, accessToken string) (*Client, error) { - hsURL, err := url.Parse(homeserverURL) - if err != nil { - return nil, err - } - // By default, use an in-memory store which will never save filter ids / next batch tokens to disk. - // The client will work with this storer: it just won't remember across restarts. - // In practice, a database backend should be used. - store := NewInMemoryStore() - cli := Client{ - AccessToken: accessToken, - HomeserverURL: hsURL, - UserID: userID, - Prefix: "/_matrix/client/r0", - Syncer: NewDefaultSyncer(userID, store), - Store: store, - } - // By default, use the default HTTP client. - cli.Client = http.DefaultClient - - return &cli, nil -} diff --git a/vendor/github.com/matrix-org/gomatrix/events.go b/vendor/github.com/matrix-org/gomatrix/events.go deleted file mode 100644 index cbc70a82..00000000 --- a/vendor/github.com/matrix-org/gomatrix/events.go +++ /dev/null @@ -1,157 +0,0 @@ -package gomatrix - -import ( - "html" - "regexp" -) - -// 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) - Redacts string `json:"redacts,omitempty"` // The event ID that was redacted if a m.room.redaction event - Unsigned map[string]interface{} `json:"unsigned"` // The unsigned portions of the event, such as age and prev_content - Content map[string]interface{} `json:"content"` // The JSON content of the event. - PrevContent map[string]interface{} `json:"prev_content,omitempty"` // The JSON prev_content of the event. -} - -// 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 - } - body, ok = value.(string) - 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) - return -} - -// TextMessage is the contents of a Matrix formated message event. -type TextMessage struct { - MsgType string `json:"msgtype"` - Body string `json:"body"` - FormattedBody string `json:"formatted_body"` - Format string `json:"format"` -} - -// ThumbnailInfo contains info about an thumbnail image - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-image -type ThumbnailInfo struct { - Height uint `json:"h,omitempty"` - Width uint `json:"w,omitempty"` - Mimetype string `json:"mimetype,omitempty"` - Size uint `json:"size,omitempty"` -} - -// 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"` - ThumbnailInfo ThumbnailInfo `json:"thumbnail_info,omitempty"` - ThumbnailURL string `json:"thumbnail_url,omitempty"` -} - -// 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 ThumbnailInfo `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"` -} - -// 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"` -} - -// 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"` -} - -// 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"` -} - -// FileInfo contains info about an file - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-file -type FileInfo struct { - Mimetype string `json:"mimetype,omitempty"` - Size uint `json:"size,omitempty"` //filesize in bytes -} - -// FileMessage is an m.file event - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-file -type FileMessage struct { - MsgType string `json:"msgtype"` - Body string `json:"body"` - URL string `json:"url"` - Filename string `json:"filename"` - Info FileInfo `json:"info,omitempty"` - ThumbnailURL string `json:"thumbnail_url,omitempty"` - ThumbnailInfo ImageInfo `json:"thumbnail_info,omitempty"` -} - -// LocationMessage is an m.location event - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-location -type LocationMessage struct { - MsgType string `json:"msgtype"` - Body string `json:"body"` - GeoURI string `json:"geo_uri"` - ThumbnailURL string `json:"thumbnail_url,omitempty"` - ThumbnailInfo ImageInfo `json:"thumbnail_info,omitempty"` -} - -// AudioInfo contains info about an file - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-audio -type AudioInfo struct { - Mimetype string `json:"mimetype,omitempty"` - Size uint `json:"size,omitempty"` //filesize in bytes - Duration uint `json:"duration,omitempty"` //audio duration in ms -} - -// AudioMessage is an m.audio event - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-audio -type AudioMessage struct { - MsgType string `json:"msgtype"` - Body string `json:"body"` - URL string `json:"url"` - Info AudioInfo `json:"info,omitempty"` -} - -var htmlRegex = regexp.MustCompile("<[^<]+?>") - -// 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, - } -} diff --git a/vendor/github.com/matrix-org/gomatrix/filter.go b/vendor/github.com/matrix-org/gomatrix/filter.go deleted file mode 100644 index 2a0c37fa..00000000 --- a/vendor/github.com/matrix-org/gomatrix/filter.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2017 Jan Christian Grünhage -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package gomatrix - -import "errors" - -//Filter is used by clients to specify how the server should filter responses to e.g. sync requests -//Specified by: https://matrix.org/docs/spec/client_server/r0.2.0.html#filtering -type Filter struct { - AccountData FilterPart `json:"account_data,omitempty"` - EventFields []string `json:"event_fields,omitempty"` - EventFormat string `json:"event_format,omitempty"` - Presence FilterPart `json:"presence,omitempty"` - Room RoomFilter `json:"room,omitempty"` -} - -// RoomFilter is used to define filtering rules for room events -type RoomFilter struct { - AccountData FilterPart `json:"account_data,omitempty"` - Ephemeral FilterPart `json:"ephemeral,omitempty"` - IncludeLeave bool `json:"include_leave,omitempty"` - NotRooms []string `json:"not_rooms,omitempty"` - Rooms []string `json:"rooms,omitempty"` - State FilterPart `json:"state,omitempty"` - Timeline FilterPart `json:"timeline,omitempty"` -} - -// FilterPart is used to define filtering rules for specific categories of events -type FilterPart struct { - NotRooms []string `json:"not_rooms,omitempty"` - Rooms []string `json:"rooms,omitempty"` - Limit int `json:"limit,omitempty"` - NotSenders []string `json:"not_senders,omitempty"` - NotTypes []string `json:"not_types,omitempty"` - Senders []string `json:"senders,omitempty"` - Types []string `json:"types,omitempty"` - ContainsURL *bool `json:"contains_url,omitempty"` -} - -// Validate checks if the filter contains valid property values -func (filter *Filter) Validate() error { - if filter.EventFormat != "client" && filter.EventFormat != "federation" { - return errors.New("Bad event_format value. Must be one of [\"client\", \"federation\"]") - } - return nil -} - -// DefaultFilter returns the default filter used by the Matrix server if no filter is provided in the request -func DefaultFilter() Filter { - return Filter{ - AccountData: DefaultFilterPart(), - EventFields: nil, - EventFormat: "client", - Presence: DefaultFilterPart(), - Room: RoomFilter{ - AccountData: DefaultFilterPart(), - Ephemeral: DefaultFilterPart(), - IncludeLeave: false, - NotRooms: nil, - Rooms: nil, - State: DefaultFilterPart(), - Timeline: DefaultFilterPart(), - }, - } -} - -// DefaultFilterPart returns the default filter part used by the Matrix server if no filter is provided in the request -func DefaultFilterPart() FilterPart { - return FilterPart{ - NotRooms: nil, - Rooms: nil, - Limit: 20, - NotSenders: nil, - NotTypes: nil, - Senders: nil, - Types: nil, - } -} diff --git a/vendor/github.com/matrix-org/gomatrix/go.mod b/vendor/github.com/matrix-org/gomatrix/go.mod deleted file mode 100644 index 18e69bf1..00000000 --- a/vendor/github.com/matrix-org/gomatrix/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/matrix-org/gomatrix - -go 1.12 diff --git a/vendor/github.com/matrix-org/gomatrix/identifier.go b/vendor/github.com/matrix-org/gomatrix/identifier.go deleted file mode 100644 index 4a61d080..00000000 --- a/vendor/github.com/matrix-org/gomatrix/identifier.go +++ /dev/null @@ -1,69 +0,0 @@ -package gomatrix - -// Identifier is the interface for https://matrix.org/docs/spec/client_server/r0.6.0#identifier-types -type Identifier interface { - // Returns the identifier type - // https://matrix.org/docs/spec/client_server/r0.6.0#identifier-types - Type() string -} - -// UserIdentifier is the Identifier for https://matrix.org/docs/spec/client_server/r0.6.0#matrix-user-id -type UserIdentifier struct { - IDType string `json:"type"` // Set by NewUserIdentifer - User string `json:"user"` -} - -// Type implements the Identifier interface -func (i UserIdentifier) Type() string { - return "m.id.user" -} - -// NewUserIdentifier creates a new UserIdentifier with IDType set to "m.id.user" -func NewUserIdentifier(user string) UserIdentifier { - return UserIdentifier{ - IDType: "m.id.user", - User: user, - } -} - -// ThirdpartyIdentifier is the Identifier for https://matrix.org/docs/spec/client_server/r0.6.0#third-party-id -type ThirdpartyIdentifier struct { - IDType string `json:"type"` // Set by NewThirdpartyIdentifier - Medium string `json:"medium"` - Address string `json:"address"` -} - -// Type implements the Identifier interface -func (i ThirdpartyIdentifier) Type() string { - return "m.id.thirdparty" -} - -// NewThirdpartyIdentifier creates a new UserIdentifier with IDType set to "m.id.user" -func NewThirdpartyIdentifier(medium, address string) ThirdpartyIdentifier { - return ThirdpartyIdentifier{ - IDType: "m.id.thirdparty", - Medium: medium, - Address: address, - } -} - -// PhoneIdentifier is the Identifier for https://matrix.org/docs/spec/client_server/r0.6.0#phone-number -type PhoneIdentifier struct { - IDType string `json:"type"` // Set by NewPhoneIdentifier - Country string `json:"country"` - Phone string `json:"phone"` -} - -// Type implements the Identifier interface -func (i PhoneIdentifier) Type() string { - return "m.id.phone" -} - -// NewPhoneIdentifier creates a new UserIdentifier with IDType set to "m.id.user" -func NewPhoneIdentifier(country, phone string) PhoneIdentifier { - return PhoneIdentifier{ - IDType: "m.id.phone", - Country: country, - Phone: phone, - } -} diff --git a/vendor/github.com/matrix-org/gomatrix/requests.go b/vendor/github.com/matrix-org/gomatrix/requests.go deleted file mode 100644 index 31c426d4..00000000 --- a/vendor/github.com/matrix-org/gomatrix/requests.go +++ /dev/null @@ -1,79 +0,0 @@ -package gomatrix - -// ReqRegister is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register -type ReqRegister struct { - Username string `json:"username,omitempty"` - BindEmail bool `json:"bind_email,omitempty"` - Password string `json:"password,omitempty"` - DeviceID string `json:"device_id,omitempty"` - InitialDeviceDisplayName string `json:"initial_device_display_name"` - Auth interface{} `json:"auth,omitempty"` -} - -// ReqLogin is the JSON request for http://matrix.org/docs/spec/client_server/r0.6.0.html#post-matrix-client-r0-login -type ReqLogin struct { - Type string `json:"type"` - Identifier Identifier `json:"identifier,omitempty"` - Password string `json:"password,omitempty"` - Medium string `json:"medium,omitempty"` - User string `json:"user,omitempty"` - Address string `json:"address,omitempty"` - Token string `json:"token,omitempty"` - DeviceID string `json:"device_id,omitempty"` - InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"` -} - -// ReqCreateRoom is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom -type ReqCreateRoom struct { - Visibility string `json:"visibility,omitempty"` - RoomAliasName string `json:"room_alias_name,omitempty"` - Name string `json:"name,omitempty"` - Topic string `json:"topic,omitempty"` - 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"` - Preset string `json:"preset,omitempty"` - IsDirect bool `json:"is_direct,omitempty"` -} - -// ReqRedact is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid -type ReqRedact struct { - Reason string `json:"reason,omitempty"` -} - -// ReqInvite3PID is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#id57 -// It is also a JSON object used in https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom -type ReqInvite3PID struct { - IDServer string `json:"id_server"` - Medium string `json:"medium"` - Address string `json:"address"` -} - -// ReqInviteUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite -type ReqInviteUser struct { - UserID string `json:"user_id"` -} - -// ReqKickUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick -type ReqKickUser struct { - Reason string `json:"reason,omitempty"` - UserID string `json:"user_id"` -} - -// ReqBanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban -type ReqBanUser struct { - Reason string `json:"reason,omitempty"` - UserID string `json:"user_id"` -} - -// ReqUnbanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban -type ReqUnbanUser struct { - UserID string `json:"user_id"` -} - -// 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"` -} diff --git a/vendor/github.com/matrix-org/gomatrix/responses.go b/vendor/github.com/matrix-org/gomatrix/responses.go deleted file mode 100644 index f488e69e..00000000 --- a/vendor/github.com/matrix-org/gomatrix/responses.go +++ /dev/null @@ -1,210 +0,0 @@ -package gomatrix - -// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface. -// See http://matrix.org/docs/spec/client_server/r0.2.0.html#api-standards -type RespError struct { - ErrCode string `json:"errcode"` - Err string `json:"error"` -} - -// Error returns the errcode and error message. -func (e RespError) Error() string { - return e.ErrCode + ": " + e.Err -} - -// RespCreateFilter is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter -type RespCreateFilter struct { - FilterID string `json:"filter_id"` -} - -// RespVersions is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions -type RespVersions struct { - Versions []string `json:"versions"` -} - -// RespPublicRooms is the JSON response for http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#get-matrix-client-unstable-publicrooms -type RespPublicRooms struct { - TotalRoomCountEstimate int `json:"total_room_count_estimate"` - PrevBatch string `json:"prev_batch"` - NextBatch string `json:"next_batch"` - Chunk []PublicRoom `json:"chunk"` -} - -// RespJoinRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-join -type RespJoinRoom struct { - RoomID string `json:"room_id"` -} - -// RespLeaveRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave -type RespLeaveRoom struct{} - -// RespForgetRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget -type RespForgetRoom struct{} - -// RespInviteUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite -type RespInviteUser struct{} - -// RespKickUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick -type RespKickUser struct{} - -// RespBanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban -type RespBanUser struct{} - -// RespUnbanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban -type RespUnbanUser struct{} - -// RespTyping is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid -type RespTyping struct{} - -// RespJoinedRooms is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680 -type RespJoinedRooms struct { - JoinedRooms []string `json:"joined_rooms"` -} - -// RespJoinedMembers is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680 -type RespJoinedMembers struct { - Joined map[string]struct { - DisplayName *string `json:"display_name"` - AvatarURL *string `json:"avatar_url"` - } `json:"joined"` -} - -// 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"` - End string `json:"end"` -} - -// RespSendEvent is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid -type RespSendEvent struct { - EventID string `json:"event_id"` -} - -// RespMediaUpload is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload -type RespMediaUpload struct { - ContentURI string `json:"content_uri"` -} - -// RespUserInteractive is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#user-interactive-authentication-api -type RespUserInteractive struct { - Flows []struct { - Stages []string `json:"stages"` - } `json:"flows"` - Params map[string]interface{} `json:"params"` - Session string `json:"session"` - Completed []string `json:"completed"` - ErrCode string `json:"errcode"` - Error string `json:"error"` -} - -// HasSingleStageFlow returns true if there exists at least 1 Flow with a single stage of stageName. -func (r RespUserInteractive) HasSingleStageFlow(stageName string) bool { - for _, f := range r.Flows { - if len(f.Stages) == 1 && f.Stages[0] == stageName { - return true - } - } - return false -} - -// RespUserDisplayName is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname -type RespUserDisplayName struct { - DisplayName string `json:"displayname"` -} - -// RespUserStatus is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-presence-userid-status -type RespUserStatus struct { - Presence string `json:"presence"` - StatusMsg string `json:"status_msg"` - LastActiveAgo int `json:"last_active_ago"` - CurrentlyActive bool `json:"currently_active"` -} - -// RespRegister is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register -type RespRegister struct { - AccessToken string `json:"access_token"` - DeviceID string `json:"device_id"` - HomeServer string `json:"home_server"` - RefreshToken string `json:"refresh_token"` - UserID string `json:"user_id"` -} - -// RespLogin is the JSON response for http://matrix.org/docs/spec/client_server/r0.6.0.html#post-matrix-client-r0-login -type RespLogin struct { - AccessToken string `json:"access_token"` - DeviceID string `json:"device_id"` - HomeServer string `json:"home_server"` - UserID string `json:"user_id"` - WellKnown DiscoveryInformation `json:"well_known"` -} - -// DiscoveryInformation is the JSON Response for https://matrix.org/docs/spec/client_server/r0.6.0#get-well-known-matrix-client and a part of the JSON Response for https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-login -type DiscoveryInformation struct { - Homeserver struct { - BaseURL string `json:"base_url"` - } `json:"m.homeserver"` - IdentityServer struct { - BaseURL string `json:"base_url"` - } `json:"m.identitiy_server"` -} - -// RespLogout is the JSON response for http://matrix.org/docs/spec/client_server/r0.6.0.html#post-matrix-client-r0-logout -type RespLogout struct{} - -// RespLogoutAll is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-logout-all -type RespLogoutAll struct{} - -// RespCreateRoom is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom -type RespCreateRoom struct { - RoomID string `json:"room_id"` -} - -// RespSync is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync -type RespSync struct { - NextBatch string `json:"next_batch"` - AccountData struct { - Events []Event `json:"events"` - } `json:"account_data"` - Presence struct { - Events []Event `json:"events"` - } `json:"presence"` - Rooms struct { - Leave map[string]struct { - State struct { - Events []Event `json:"events"` - } `json:"state"` - Timeline struct { - Events []Event `json:"events"` - Limited bool `json:"limited"` - PrevBatch string `json:"prev_batch"` - } `json:"timeline"` - } `json:"leave"` - Join map[string]struct { - State struct { - Events []Event `json:"events"` - } `json:"state"` - Timeline struct { - Events []Event `json:"events"` - Limited bool `json:"limited"` - PrevBatch string `json:"prev_batch"` - } `json:"timeline"` - Ephemeral struct { - Events []Event `json:"events"` - } `json:"ephemeral"` - } `json:"join"` - Invite map[string]struct { - State struct { - Events []Event - } `json:"invite_state"` - } `json:"invite"` - } `json:"rooms"` -} - -// RespTurnServer is the JSON response from a Turn Server -type RespTurnServer struct { - Username string `json:"username"` - Password string `json:"password"` - TTL int `json:"ttl"` - URIs []string `json:"uris"` -} diff --git a/vendor/github.com/matrix-org/gomatrix/room.go b/vendor/github.com/matrix-org/gomatrix/room.go deleted file mode 100644 index 364deab2..00000000 --- a/vendor/github.com/matrix-org/gomatrix/room.go +++ /dev/null @@ -1,63 +0,0 @@ -package gomatrix - -// Room represents a single Matrix room. -type Room struct { - ID string - State map[string]map[string]*Event -} - -// PublicRoom represents the information about a public room obtainable from the room directory -type PublicRoom struct { - CanonicalAlias string `json:"canonical_alias"` - Name string `json:"name"` - WorldReadable bool `json:"world_readable"` - Topic string `json:"topic"` - NumJoinedMembers int `json:"num_joined_members"` - AvatarURL string `json:"avatar_url"` - RoomID string `json:"room_id"` - GuestCanJoin bool `json:"guest_can_join"` - Aliases []string `json:"aliases"` -} - -// UpdateState updates the room's current state with the given Event. This will clobber events based -// on the type/state_key combination. -func (room Room) UpdateState(event *Event) { - _, exists := room.State[event.Type] - if !exists { - room.State[event.Type] = make(map[string]*Event) - } - room.State[event.Type][*event.StateKey] = event -} - -// GetStateEvent returns the state event for the given type/state_key combo, or nil. -func (room Room) GetStateEvent(eventType string, stateKey string) *Event { - stateEventMap := room.State[eventType] - event := stateEventMap[stateKey] - return 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) - if event != nil { - membershipState, found := event.Content["membership"] - if found { - mState, isString := membershipState.(string) - if isString { - state = mState - } - } - } - return state -} - -// NewRoom creates a new Room with the given ID -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), - } -} diff --git a/vendor/github.com/matrix-org/gomatrix/store.go b/vendor/github.com/matrix-org/gomatrix/store.go deleted file mode 100644 index 6dc687e5..00000000 --- a/vendor/github.com/matrix-org/gomatrix/store.go +++ /dev/null @@ -1,65 +0,0 @@ -package gomatrix - -// Storer is an interface which must be satisfied to store client data. -// -// You can either write a struct which persists this data to disk, or you can use the -// provided "InMemoryStore" which just keeps data around in-memory which is lost on -// restarts. -type Storer interface { - SaveFilterID(userID, filterID string) - LoadFilterID(userID string) string - SaveNextBatch(userID, nextBatchToken string) - LoadNextBatch(userID string) string - SaveRoom(room *Room) - LoadRoom(roomID string) *Room -} - -// InMemoryStore implements the Storer interface. -// -// Everything is persisted in-memory as maps. It is not safe to load/save filter IDs -// or next batch tokens on any goroutine other than the syncing goroutine: the one -// which called Client.Sync(). -type InMemoryStore struct { - Filters map[string]string - NextBatch map[string]string - Rooms map[string]*Room -} - -// SaveFilterID to memory. -func (s *InMemoryStore) SaveFilterID(userID, filterID string) { - s.Filters[userID] = filterID -} - -// LoadFilterID from memory. -func (s *InMemoryStore) LoadFilterID(userID string) string { - return s.Filters[userID] -} - -// SaveNextBatch to memory. -func (s *InMemoryStore) SaveNextBatch(userID, nextBatchToken string) { - s.NextBatch[userID] = nextBatchToken -} - -// LoadNextBatch from memory. -func (s *InMemoryStore) LoadNextBatch(userID string) string { - return s.NextBatch[userID] -} - -// SaveRoom to memory. -func (s *InMemoryStore) SaveRoom(room *Room) { - s.Rooms[room.ID] = room -} - -// LoadRoom from memory. -func (s *InMemoryStore) LoadRoom(roomID string) *Room { - return s.Rooms[roomID] -} - -// NewInMemoryStore constructs a new InMemoryStore. -func NewInMemoryStore() *InMemoryStore { - return &InMemoryStore{ - Filters: make(map[string]string), - NextBatch: make(map[string]string), - Rooms: make(map[string]*Room), - } -} diff --git a/vendor/github.com/matrix-org/gomatrix/sync.go b/vendor/github.com/matrix-org/gomatrix/sync.go deleted file mode 100644 index ac326c3a..00000000 --- a/vendor/github.com/matrix-org/gomatrix/sync.go +++ /dev/null @@ -1,168 +0,0 @@ -package gomatrix - -import ( - "encoding/json" - "fmt" - "runtime/debug" - "time" -) - -// Syncer represents an interface that must be satisfied in order to do /sync requests on a client. -type Syncer interface { - // Process the /sync response. The since parameter is the since= value that was used to produce the response. - // This is useful for detecting the very first sync (since=""). If an error is return, Syncing will be stopped - // permanently. - ProcessResponse(resp *RespSync, since string) error - // OnFailedSync returns either the time to wait before retrying or an error to stop syncing permanently. - OnFailedSync(res *RespSync, err error) (time.Duration, error) - // GetFilterJSON for the given user ID. NOT the filter ID. - GetFilterJSON(userID string) json.RawMessage -} - -// DefaultSyncer is the default syncing implementation. You can either write your own syncer, or selectively -// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer -// pattern to notify callers about incoming events. See DefaultSyncer.OnEventType for more information. -type DefaultSyncer struct { - UserID string - Store Storer - listeners map[string][]OnEventListener // event type to listeners array -} - -// OnEventListener can be used with DefaultSyncer.OnEventType to be informed of incoming events. -type OnEventListener func(*Event) - -// NewDefaultSyncer returns an instantiated DefaultSyncer -func NewDefaultSyncer(userID string, store Storer) *DefaultSyncer { - return &DefaultSyncer{ - UserID: userID, - Store: store, - listeners: make(map[string][]OnEventListener), - } -} - -// ProcessResponse processes the /sync response in a way suitable for bots. "Suitable for bots" means a stream of -// unrepeating events. Returns a fatal error if a listener panics. -func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error) { - if !s.shouldProcessResponse(res, since) { - return - } - - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("ProcessResponse panicked! userID=%s since=%s panic=%s\n%s", s.UserID, since, r, debug.Stack()) - } - }() - - for roomID, roomData := range res.Rooms.Join { - room := s.getOrCreateRoom(roomID) - for _, event := range roomData.State.Events { - event.RoomID = roomID - room.UpdateState(&event) - s.notifyListeners(&event) - } - for _, event := range roomData.Timeline.Events { - event.RoomID = roomID - s.notifyListeners(&event) - } - for _, event := range roomData.Ephemeral.Events { - event.RoomID = roomID - s.notifyListeners(&event) - } - } - for roomID, roomData := range res.Rooms.Invite { - room := s.getOrCreateRoom(roomID) - for _, event := range roomData.State.Events { - event.RoomID = roomID - room.UpdateState(&event) - s.notifyListeners(&event) - } - } - for roomID, roomData := range res.Rooms.Leave { - room := s.getOrCreateRoom(roomID) - for _, event := range roomData.Timeline.Events { - if event.StateKey != nil { - event.RoomID = roomID - room.UpdateState(&event) - s.notifyListeners(&event) - } - } - } - return -} - -// 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) { - _, exists := s.listeners[eventType] - if !exists { - s.listeners[eventType] = []OnEventListener{} - } - s.listeners[eventType] = append(s.listeners[eventType], callback) -} - -// shouldProcessResponse returns true if the response should be processed. May modify the response to remove -// stuff that shouldn't be processed. -func (s *DefaultSyncer) shouldProcessResponse(resp *RespSync, since string) bool { - if since == "" { - return false - } - // This is a horrible hack because /sync will return the most recent messages for a room - // as soon as you /join it. We do NOT want to process those events in that particular room - // because they may have already been processed (if you toggle the bot in/out of the room). - // - // Work around this by inspecting each room's timeline and seeing if an m.room.member event for us - // exists and is "join" and then discard processing that room entirely if so. - // TODO: We probably want to process messages from after the last join event in the timeline. - 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" { - _, ok := resp.Rooms.Join[roomID] - if !ok { - continue - } - delete(resp.Rooms.Join, roomID) // don't re-process messages - delete(resp.Rooms.Invite, roomID) // don't re-process invites - break - } - } - } - } - return true -} - -// getOrCreateRoom must only be called by the Sync() goroutine which calls ProcessResponse() -func (s *DefaultSyncer) getOrCreateRoom(roomID string) *Room { - room := s.Store.LoadRoom(roomID) - if room == nil { // create a new Room - room = NewRoom(roomID) - s.Store.SaveRoom(room) - } - return room -} - -func (s *DefaultSyncer) notifyListeners(event *Event) { - listeners, exists := s.listeners[event.Type] - if !exists { - return - } - for _, fn := range listeners { - fn(event) - } -} - -// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error. -func (s *DefaultSyncer) OnFailedSync(res *RespSync, err error) (time.Duration, error) { - return 10 * time.Second, nil -} - -// GetFilterJSON returns a filter with a timeline limit of 50. -func (s *DefaultSyncer) GetFilterJSON(userID string) json.RawMessage { - return json.RawMessage(`{"room":{"timeline":{"limit":50}}}`) -} diff --git a/vendor/github.com/matrix-org/gomatrix/tags.go b/vendor/github.com/matrix-org/gomatrix/tags.go deleted file mode 100644 index 956fb11e..00000000 --- a/vendor/github.com/matrix-org/gomatrix/tags.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2019 Sumukha PK -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package gomatrix - -// TagContent contains the data for an m.tag message type -// https://matrix.org/docs/spec/client_server/r0.4.0.html#m-tag -type TagContent struct { - Tags map[string]TagProperties `json:"tags"` -} - -// TagProperties contains the properties of a Tag -type TagProperties struct { - Order float32 `json:"order,omitempty"` // Empty values must be neglected -} diff --git a/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go b/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go new file mode 100644 index 00000000..593f6530 --- /dev/null +++ b/vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go @@ -0,0 +1,77 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC +2898 / PKCS #5 v2.0. + +A key derivation function is useful when encrypting data based on a password +or any other not-fully-random data. It uses a pseudorandom function to derive +a secure encryption key based on the password. + +While v2.0 of the standard defines only one pseudorandom function to use, +HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved +Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To +choose, you can pass the `New` functions from the different SHA packages to +pbkdf2.Key. +*/ +package pbkdf2 // import "golang.org/x/crypto/pbkdf2" + +import ( + "crypto/hmac" + "hash" +) + +// Key derives a key from the password, salt and iteration count, returning a +// []byte of length keylen that can be used as cryptographic key. The key is +// derived based on the method described as PBKDF2 with the HMAC variant using +// the supplied hash function. +// +// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you +// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by +// doing: +// +// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New) +// +// Remember to get a good random salt. At least 8 bytes is recommended by the +// RFC. +// +// Using a higher iteration count will increase the cost of an exhaustive +// search but will also make derivation proportionally slower. +func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte { + prf := hmac.New(h, password) + hashLen := prf.Size() + numBlocks := (keyLen + hashLen - 1) / hashLen + + var buf [4]byte + dk := make([]byte, 0, numBlocks*hashLen) + U := make([]byte, hashLen) + for block := 1; block <= numBlocks; block++ { + // N.B.: || means concatenation, ^ means XOR + // for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter + // U_1 = PRF(password, salt || uint(i)) + prf.Reset() + prf.Write(salt) + buf[0] = byte(block >> 24) + buf[1] = byte(block >> 16) + buf[2] = byte(block >> 8) + buf[3] = byte(block) + prf.Write(buf[:4]) + dk = prf.Sum(dk) + T := dk[len(dk)-hashLen:] + copy(U, T) + + // U_n = PRF(password, U_(n-1)) + for n := 2; n <= iter; n++ { + prf.Reset() + prf.Write(U) + U = U[:0] + U = prf.Sum(U) + for x := range U { + T[x] ^= U[x] + } + } + } + return dk[:keyLen] +} diff --git a/vendor/maunium.net/go/mautrix/.gitignore b/vendor/maunium.net/go/mautrix/.gitignore new file mode 100644 index 00000000..66f8fb50 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/.gitignore @@ -0,0 +1,2 @@ +.idea/ +.vscode/ diff --git a/vendor/maunium.net/go/mautrix/LICENSE b/vendor/maunium.net/go/mautrix/LICENSE new file mode 100644 index 00000000..52d13511 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/LICENSE @@ -0,0 +1,374 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + 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/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + diff --git a/vendor/maunium.net/go/mautrix/README.md b/vendor/maunium.net/go/mautrix/README.md new file mode 100644 index 00000000..5a893ddf --- /dev/null +++ b/vendor/maunium.net/go/mautrix/README.md @@ -0,0 +1,24 @@ +# mautrix-go +[![GoDoc](https://godoc.org/maunium.net/go/mautrix?status.svg)](https://godoc.org/maunium.net/go/mautrix) + +A Golang Matrix framework. Used by [gomuks](https://matrix.org/docs/projects/client/gomuks), +[go-neb](https://github.com/matrix-org/go-neb), [mautrix-whatsapp](https://github.com/tulir/mautrix-whatsapp) +and others. + +Matrix room: [`#maunium:maunium.net`](https://matrix.to/#/#maunium:maunium.net) + +This project is based on [matrix-org/gomatrix](https://github.com/matrix-org/gomatrix). +The original project is licensed under [Apache 2.0](https://github.com/matrix-org/gomatrix/blob/master/LICENSE). + +In addition to the basic client API features the original project has, this framework also has: + +* Appservice support (Intent API like mautrix-python, room state storage, etc) +* End-to-end encryption support (incl. interactive SAS verification) +* Structs for parsing event content +* Helpers for parsing and generating Matrix HTML +* Helpers for handling push rules + +This project contains modules that are licensed under Apache 2.0: + +* [maunium.net/go/mautrix/crypto/canonicaljson](crypto/canonicaljson) +* [maunium.net/go/mautrix/crypto/olm](crypto/olm) diff --git a/vendor/maunium.net/go/mautrix/client.go b/vendor/maunium.net/go/mautrix/client.go new file mode 100644 index 00000000..8f78176d --- /dev/null +++ b/vendor/maunium.net/go/mautrix/client.go @@ -0,0 +1,1344 @@ +// Package mautrix implements the Matrix Client-Server API. +// +// Specification can be found at http://matrix.org/docs/spec/client_server/r0.4.0.html +package mautrix + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "strings" + "sync/atomic" + "time" + + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" + "maunium.net/go/mautrix/pushrules" +) + +type Logger interface { + Debugfln(message string, args ...interface{}) +} + +type WarnLogger interface { + Logger + Warnfln(message string, args ...interface{}) +} + +type Stringifiable interface { + String() string +} + +// Client represents a Matrix client. +type Client struct { + HomeserverURL *url.URL // The base homeserver URL + Prefix URLPath // The API prefix eg '/_matrix/client/r0' + UserID id.UserID // The user ID of the client. Used for forming HTTP paths which use the client's user ID. + DeviceID id.DeviceID // The device ID of the client. + AccessToken string // The access_token for the client. + UserAgent string // The value for the User-Agent header + 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 Logger + SyncPresence event.Presence + + // Number of times that mautrix will retry any HTTP request + // if the request fails entirely or returns a HTTP gateway error (502-504) + DefaultHTTPRetries int + + txnID int32 + + // 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. + // See http://matrix.org/docs/spec/application_service/unstable.html#identity-assertion + AppServiceUserID id.UserID + + syncingID uint32 // Identifies the current Sync. Only one Sync can be active at any given time. +} + +type ClientWellKnown struct { + Homeserver HomeserverInfo `json:"m.homeserver"` + IdentityServer IdentityServerInfo `json:"m.identity_server"` +} + +type HomeserverInfo struct { + BaseURL string `json:"base_url"` +} + +type IdentityServerInfo struct { + BaseURL string `json:"base_url"` +} + +// DiscoverClientAPI resolves the client API URL from a Matrix server name. +// Use ParseUserID to extract the server name from a user ID. +// https://matrix.org/docs/spec/client_server/r0.6.0#server-discovery +func DiscoverClientAPI(serverName string) (*ClientWellKnown, error) { + wellKnownURL := url.URL{ + Scheme: "https", + Host: serverName, + Path: "/.well-known/matrix/client", + } + + req, err := http.NewRequest("GET", wellKnownURL.String(), nil) + if err != nil { + return nil, err + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", DefaultUserAgent+" .well-known fetcher") + + client := &http.Client{Timeout: 30 * time.Second} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return nil, nil + } + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var wellKnown ClientWellKnown + err = json.Unmarshal(data, &wellKnown) + if err != nil { + return nil, errors.New(".well-known response not JSON") + } + + return &wellKnown, nil +} + +// BuildURL builds a URL with the Client's homserver/prefix/access_token set already. +func (cli *Client) BuildURL(urlPath ...interface{}) string { + return cli.BuildBaseURL(append(cli.Prefix, urlPath...)...) +} + +// BuildBaseURL builds a URL with the Client's homeserver/access_token set already. You must +// supply the prefix in the path. +func (cli *Client) BuildBaseURL(urlPath ...interface{}) string { + // copy the URL. Purposefully ignore error as the input is from a valid URL already + hsURL, _ := url.Parse(cli.HomeserverURL.String()) + if hsURL.Scheme == "" { + hsURL.Scheme = "https" + } + rawParts := make([]string, len(urlPath)+1) + rawParts[0] = hsURL.RawPath + parts := make([]string, len(urlPath)+1) + parts[0] = hsURL.Path + for i, part := range urlPath { + switch casted := part.(type) { + case string: + parts[i+1] = casted + case int: + parts[i+1] = strconv.Itoa(casted) + case Stringifiable: + parts[i+1] = casted.String() + default: + parts[i+1] = fmt.Sprint(casted) + } + rawParts[i+1] = url.PathEscape(parts[i+1]) + } + hsURL.Path = strings.Join(parts, "/") + hsURL.RawPath = strings.Join(rawParts, "/") + query := hsURL.Query() + if cli.AppServiceUserID != "" { + query.Set("user_id", string(cli.AppServiceUserID)) + } + hsURL.RawQuery = query.Encode() + return hsURL.String() +} + +type URLPath = []interface{} + +// BuildURLWithQuery builds a URL with query parameters in addition to the Client's homeserver/prefix/access_token set already. +func (cli *Client) BuildURLWithQuery(urlPath URLPath, urlQuery map[string]string) string { + u, _ := url.Parse(cli.BuildURL(urlPath...)) + q := u.Query() + for k, v := range urlQuery { + q.Set(k, v) + } + u.RawQuery = q.Encode() + return u.String() +} + +// SetCredentials sets the user ID and access token on this client instance. +// +// Deprecated: use the StoreCredentials field in ReqLogin instead. +func (cli *Client) SetCredentials(userID id.UserID, accessToken string) { + cli.AccessToken = accessToken + cli.UserID = userID +} + +// ClearCredentials removes the user ID and access token on this client instance. +func (cli *Client) ClearCredentials() { + cli.AccessToken = "" + cli.UserID = "" + cli.DeviceID = "" +} + +// Sync starts syncing with the provided Homeserver. If Sync() is called twice then the first sync will be stopped and the +// error will be nil. +// +// This function will block until a fatal /sync error occurs, so it should almost always be started as a new goroutine. +// Fatal sync errors can be caused by: +// - The failure to create a filter. +// - Client.Syncer.OnFailedSync returning an error in response to a failed sync. +// - Client.Syncer.ProcessResponse returning an error. +// If you wish to continue retrying in spite of these fatal errors, call Sync() again. +func (cli *Client) Sync() error { + return cli.SyncWithContext(context.Background()) +} + +func (cli *Client) SyncWithContext(ctx context.Context) error { + // Mark the client as syncing. + // We will keep syncing until the syncing state changes. Either because + // Sync is called or StopSync is called. + syncingID := cli.incrementSyncingID() + nextBatch := cli.Store.LoadNextBatch(cli.UserID) + filterID := cli.Store.LoadFilterID(cli.UserID) + if filterID == "" { + filterJSON := cli.Syncer.GetFilterJSON(cli.UserID) + resFilter, err := cli.CreateFilter(filterJSON) + if err != nil { + return err + } + filterID = resFilter.FilterID + cli.Store.SaveFilterID(cli.UserID, filterID) + } + for { + resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, cli.SyncPresence, ctx) + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + duration, err2 := cli.Syncer.OnFailedSync(resSync, err) + if err2 != nil { + return err2 + } + time.Sleep(duration) + continue + } + + // Check that the syncing state hasn't changed + // Either because we've stopped syncing or another sync has been started. + // We discard the response from our sync. + if cli.getSyncingID() != syncingID { + return nil + } + + // Save the token now *before* processing it. This means it's possible + // to not process some events, but it means that we won't get constantly stuck processing + // a malformed/buggy event which keeps making us panic. + cli.Store.SaveNextBatch(cli.UserID, resSync.NextBatch) + if err = cli.Syncer.ProcessResponse(resSync, nextBatch); err != nil { + return err + } + + nextBatch = resSync.NextBatch + } +} + +func (cli *Client) incrementSyncingID() uint32 { + return atomic.AddUint32(&cli.syncingID, 1) +} + +func (cli *Client) getSyncingID() uint32 { + return atomic.LoadUint32(&cli.syncingID) +} + +// StopSync stops the ongoing sync started by Sync. +func (cli *Client) StopSync() { + // Advance the syncing state so that any running Syncs will terminate. + cli.incrementSyncingID() +} + +const logBodyContextKey = "fi.mau.mautrix.log_body" +const logRequestIDContextKey = "fi.mau.mautrix.request_id" + +func (cli *Client) LogRequest(req *http.Request) { + if cli.Logger == nil { + return + } + body, ok := req.Context().Value(logBodyContextKey).(string) + reqID, _ := req.Context().Value(logRequestIDContextKey).(int) + if ok && len(body) > 0 { + cli.Logger.Debugfln("req #%d: %s %s %s", reqID, req.Method, req.URL.String(), body) + } else { + cli.Logger.Debugfln("req #%d: %s %s", reqID, req.Method, req.URL.String()) + } +} + +func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) ([]byte, error) { + return cli.MakeFullRequest(FullRequest{Method: method, URL: httpURL, RequestJSON: reqBody, ResponseJSON: resBody}) +} + +type FullRequest struct { + Method string + URL string + Headers http.Header + RequestJSON interface{} + RequestBody io.Reader + RequestLength int64 + ResponseJSON interface{} + Context context.Context + MaxAttempts int +} + +var requestID int32 + +func (params *FullRequest) compileRequest() (*http.Request, error) { + var logBody string + reqBody := params.RequestBody + if params.Context == nil { + params.Context = context.Background() + } + if params.RequestJSON != nil { + jsonStr, err := json.Marshal(params.RequestJSON) + if err != nil { + return nil, HTTPError{ + Message: "failed to marshal JSON", + WrappedError: err, + } + } + logBody = string(jsonStr) + reqBody = bytes.NewBuffer(jsonStr) + } else if params.RequestLength > 0 && params.RequestBody != nil { + logBody = fmt.Sprintf("%d bytes", params.RequestLength) + } + ctx := context.WithValue(params.Context, logBodyContextKey, logBody) + reqID := atomic.AddInt32(&requestID, 1) + ctx = context.WithValue(ctx, logRequestIDContextKey, int(reqID)) + req, err := http.NewRequestWithContext(ctx, params.Method, params.URL, reqBody) + if err != nil { + return nil, HTTPError{ + Message: "failed to create request", + WrappedError: err, + } + } + if params.Headers != nil { + req.Header = params.Headers + } + if params.RequestJSON != nil { + req.Header.Set("Content-Type", "application/json") + } + if params.RequestLength > 0 && params.RequestBody != nil { + req.ContentLength = params.RequestLength + } + return req, nil +} + +// MakeFullRequest makes a JSON HTTP request to the given URL. +// If "resBody" is not nil, the response body will be json.Unmarshalled into it. +// +// Returns the HTTP body as bytes on 2xx with a nil error. Returns an error if the response is not 2xx along +// with the HTTP body bytes if it got that far. This error is an HTTPError which includes the returned +// HTTP status code and possibly a RespError as the WrappedError, if the HTTP body could be decoded as a RespError. +func (cli *Client) MakeFullRequest(params FullRequest) ([]byte, error) { + if params.MaxAttempts == 0 { + params.MaxAttempts = 1 + cli.DefaultHTTPRetries + } + req, err := params.compileRequest() + if err != nil { + return nil, err + } + req.Header.Set("User-Agent", cli.UserAgent) + if len(cli.AccessToken) > 0 { + req.Header.Set("Authorization", "Bearer "+cli.AccessToken) + } + return cli.executeCompiledRequest(req, params.MaxAttempts-1, 4*time.Second, params.ResponseJSON) +} + +func (cli *Client) logWarning(format string, args ...interface{}) { + warnLogger, ok := cli.Logger.(WarnLogger) + if ok { + warnLogger.Warnfln(format, args...) + } else { + cli.Logger.Debugfln(format, args...) + } +} + +func (cli *Client) doRetry(req *http.Request, cause error, retries int, backoff time.Duration, responseJSON interface{}) ([]byte, error) { + reqID, _ := req.Context().Value(logRequestIDContextKey).(int) + if req.Body != nil { + if req.GetBody == nil { + cli.logWarning("Failed to get new body to retry request #%d: GetBody is nil", reqID) + return nil, cause + } + var err error + req.Body, err = req.GetBody() + if err != nil { + cli.logWarning("Failed to get new body to retry request #%d: %v", reqID, err) + return nil, cause + } + } + cli.logWarning("Request #%d failed: %v, retrying in %d seconds", reqID, cause, int(backoff.Seconds())) + time.Sleep(backoff) + return cli.executeCompiledRequest(req, retries-1, backoff*2, responseJSON) +} + +func (cli *Client) executeCompiledRequest(req *http.Request, retries int, backoff time.Duration, responseJSON interface{}) ([]byte, error) { + cli.LogRequest(req) + res, err := cli.Client.Do(req) + if res != nil { + defer res.Body.Close() + } + if err != nil { + if retries > 0 { + return cli.doRetry(req, err, retries, backoff, responseJSON) + } + return nil, HTTPError{ + Request: req, + Response: res, + + Message: "request error", + WrappedError: err, + } + } + + if retries > 0 && (res.StatusCode == http.StatusBadGateway || res.StatusCode == http.StatusServiceUnavailable || res.StatusCode == http.StatusGatewayTimeout) { + return cli.doRetry(req, fmt.Errorf("HTTP %d", res.StatusCode), retries, backoff, responseJSON) + } + + contents, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, HTTPError{ + Request: req, + Response: res, + + Message: "failed to read response body", + WrappedError: err, + } + } + + if res.StatusCode < 200 || res.StatusCode >= 300 { + respErr := &RespError{} + if _ = json.Unmarshal(contents, respErr); respErr.ErrCode == "" { + respErr = nil + } + + return contents, HTTPError{ + Request: req, + Response: res, + RespError: respErr, + } + } + + if responseJSON == nil { + return contents, nil + } + + if err = json.Unmarshal(contents, &responseJSON); err != nil { + return nil, HTTPError{ + Request: req, + Response: res, + + Message: "failed to unmarshal response body", + ResponseBody: string(contents), + WrappedError: err, + } + } + + return contents, nil +} + +// Whoami gets the user ID of the current user. See https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-join +func (cli *Client) Whoami() (resp *RespWhoami, err error) { + urlPath := cli.BuildURL("account", "whoami") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +// CreateFilter makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter +func (cli *Client) CreateFilter(filter *Filter) (resp *RespCreateFilter, err error) { + urlPath := cli.BuildURL("user", cli.UserID, "filter") + _, err = cli.MakeRequest("POST", urlPath, filter, &resp) + return +} + +// SyncRequest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync +func (cli *Client) SyncRequest(timeout int, since, filterID string, fullState bool, setPresence event.Presence, ctx context.Context) (resp *RespSync, err error) { + query := map[string]string{ + "timeout": strconv.Itoa(timeout), + } + if since != "" { + query["since"] = since + } + if filterID != "" { + query["filter"] = filterID + } + if setPresence != "" { + query["set_presence"] = string(setPresence) + } + if fullState { + query["full_state"] = "true" + } + urlPath := cli.BuildURLWithQuery(URLPath{"sync"}, query) + _, err = cli.MakeFullRequest(FullRequest{ + Method: http.MethodGet, + URL: urlPath, + ResponseJSON: &resp, + Context: ctx, + // We don't want automatic retries for SyncRequest, the Sync() wrapper handles those. + MaxAttempts: 1, + }) + return +} + +func (cli *Client) register(url string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) { + var bodyBytes []byte + bodyBytes, err = cli.MakeRequest("POST", url, req, nil) + if err != nil { + httpErr, ok := err.(HTTPError) + // if response has a 401 status, but doesn't have the errcode field, it's probably a UIA response. + if ok && httpErr.IsStatus(http.StatusUnauthorized) && httpErr.RespError == nil { + err = json.Unmarshal(bodyBytes, &uiaResp) + } + } else { + // body should be RespRegister + err = json.Unmarshal(bodyBytes, &resp) + } + return +} + +// Register makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register +// +// Registers with kind=user. For kind=guest, see RegisterGuest. +func (cli *Client) Register(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) { + u := cli.BuildURL("register") + return cli.register(u, req) +} + +// RegisterGuest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register +// with kind=guest. +// +// For kind=user, see Register. +func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) { + query := map[string]string{ + "kind": "guest", + } + u := cli.BuildURLWithQuery(URLPath{"register"}, query) + return cli.register(u, req) +} + +// RegisterDummy performs m.login.dummy registration according to https://matrix.org/docs/spec/client_server/r0.2.0.html#dummy-auth +// +// Only a username and password need to be provided on the ReqRegister struct. Most local/developer homeservers will allow registration +// this way. If the homeserver does not, an error is returned. +// +// This does not set credentials on the client instance. See SetCredentials() instead. +// +// res, err := cli.RegisterDummy(&mautrix.ReqRegister{ +// Username: "alice", +// Password: "wonderland", +// }) +// if err != nil { +// panic(err) +// } +// token := res.AccessToken +func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) { + res, uia, err := cli.Register(req) + if err != nil && uia == nil { + return nil, err + } else if uia == nil { + return nil, errors.New("server did not return user-interactive auth flows") + } else if !uia.HasSingleStageFlow(AuthTypeDummy) { + return nil, errors.New("server does not support m.login.dummy") + } + req.Auth = BaseAuthData{Type: AuthTypeDummy, Session: uia.Session} + res, _, err = cli.Register(req) + if err != nil { + return nil, err + } + return res, nil +} + +func (cli *Client) GetLoginFlows() (resp *RespLoginFlows, err error) { + urlPath := cli.BuildURL("login") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +// Login a user to the homeserver according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login +func (cli *Client) Login(req *ReqLogin) (resp *RespLogin, err error) { + urlPath := cli.BuildURL("login") + _, err = cli.MakeRequest("POST", urlPath, req, &resp) + if req.StoreCredentials && err == nil { + cli.DeviceID = resp.DeviceID + cli.AccessToken = resp.AccessToken + cli.UserID = resp.UserID + // TODO update cli.HomeserverURL based on the .well-known data in the login response + } + return +} + +// Logout the current user. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout +// This does not clear the credentials from the client instance. See ClearCredentials() instead. +func (cli *Client) Logout() (resp *RespLogout, err error) { + urlPath := cli.BuildURL("logout") + _, err = cli.MakeRequest("POST", urlPath, nil, &resp) + return +} + +// LogoutAll logs out all the devices of the current user. See https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-logout-all +// This does not clear the credentials from the client instance. See ClearCredentials() instead. +func (cli *Client) LogoutAll() (resp *RespLogout, err error) { + urlPath := cli.BuildURL("logout", "all") + _, err = cli.MakeRequest("POST", urlPath, nil, &resp) + return +} + +// Versions returns the list of supported Matrix versions on this homeserver. See http://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-versions +func (cli *Client) Versions() (resp *RespVersions, err error) { + urlPath := cli.BuildBaseURL("_matrix", "client", "versions") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +// JoinRoom joins the client to a room ID or alias. See http://matrix.org/docs/spec/client_server/r0.6.1.html#post-matrix-client-r0-join-roomidoralias +// +// If serverName is specified, this will be added as a query param to instruct the homeserver to join via that server. If content is specified, it will +// be JSON encoded and used as the request body. +func (cli *Client) JoinRoom(roomIDorAlias, serverName string, content interface{}) (resp *RespJoinRoom, err error) { + var urlPath string + if serverName != "" { + urlPath = cli.BuildURLWithQuery(URLPath{"join", roomIDorAlias}, map[string]string{ + "server_name": serverName, + }) + } else { + urlPath = cli.BuildURL("join", roomIDorAlias) + } + _, err = cli.MakeRequest("POST", urlPath, content, &resp) + return +} + +// JoinRoomByID joins the client to a room ID. See https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-join +// +// Unlike JoinRoom, this method can only be used to join rooms that the server already knows about. +// It's mostly intended for bridges and other things where it's already certain that the server is in the room. +func (cli *Client) JoinRoomByID(roomID id.RoomID) (resp *RespJoinRoom, err error) { + _, err = cli.MakeRequest("POST", cli.BuildURL("rooms", roomID, "join"), nil, &resp) + return +} + +// GetDisplayName returns the display name of the user with the specified MXID. See https://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-r0-profile-userid-displayname +func (cli *Client) GetDisplayName(mxid id.UserID) (resp *RespUserDisplayName, err error) { + urlPath := cli.BuildURL("profile", mxid, "displayname") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +// GetOwnDisplayName returns the user's display name. See https://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-r0-profile-userid-displayname +func (cli *Client) GetOwnDisplayName() (resp *RespUserDisplayName, err error) { + return cli.GetDisplayName(cli.UserID) +} + +// SetDisplayName sets the user's profile display name. See http://matrix.org/docs/spec/client_server/r0.6.1.html#put-matrix-client-r0-profile-userid-displayname +func (cli *Client) SetDisplayName(displayName string) (err error) { + urlPath := cli.BuildURL("profile", cli.UserID, "displayname") + s := struct { + DisplayName string `json:"displayname"` + }{displayName} + _, err = cli.MakeRequest("PUT", urlPath, &s, nil) + return +} + +// GetAvatarURL gets the avatar URL of the user with the specified MXID. See http://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-r0-profile-userid-avatar-url +func (cli *Client) GetAvatarURL(mxid id.UserID) (url id.ContentURI, err error) { + urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url") + s := struct { + AvatarURL id.ContentURI `json:"avatar_url"` + }{} + + _, err = cli.MakeRequest("GET", urlPath, nil, &s) + if err != nil { + return + } + url = s.AvatarURL + return +} + +// GetOwnAvatarURL gets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-r0-profile-userid-avatar-url +func (cli *Client) GetOwnAvatarURL() (url id.ContentURI, err error) { + return cli.GetAvatarURL(cli.UserID) +} + +// SetAvatarURL sets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.6.1.html#put-matrix-client-r0-profile-userid-avatar-url +func (cli *Client) SetAvatarURL(url id.ContentURI) (err error) { + urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url") + s := struct { + AvatarURL string `json:"avatar_url"` + }{url.String()} + _, err = cli.MakeRequest("PUT", urlPath, &s, nil) + if err != nil { + return err + } + + return nil +} + +// GetAccountData gets the user's account data of this type. See https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-user-userid-account-data-type +func (cli *Client) GetAccountData(name string, output interface{}) (err error) { + urlPath := cli.BuildURL("user", cli.UserID, "account_data", name) + _, err = cli.MakeRequest("GET", urlPath, nil, output) + return +} + +// SetAccountData sets the user's account data of this type. See https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-user-userid-account-data-type +func (cli *Client) SetAccountData(name string, data interface{}) (err error) { + urlPath := cli.BuildURL("user", cli.UserID, "account_data", name) + _, err = cli.MakeRequest("PUT", urlPath, &data, nil) + if err != nil { + return err + } + + return nil +} + +func (cli *Client) GetRoomAccountData(roomID id.RoomID, name string, output interface{}) (err error) { + urlPath := cli.BuildURL("user", cli.UserID, "rooms", roomID, "account_data", name) + _, err = cli.MakeRequest("GET", urlPath, nil, output) + return +} + +func (cli *Client) SetRoomAccountData(roomID id.RoomID, name string, data interface{}) (err error) { + urlPath := cli.BuildURL("user", cli.UserID, "rooms", roomID, "account_data", name) + _, err = cli.MakeRequest("PUT", urlPath, &data, nil) + if err != nil { + return err + } + + return nil +} + +type ReqSendEvent struct { + Timestamp int64 + TransactionID string + + ParentID string + RelType event.RelationType +} + +// 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 id.RoomID, eventType event.Type, contentJSON interface{}, extra ...ReqSendEvent) (resp *RespSendEvent, err error) { + var req ReqSendEvent + if len(extra) > 0 { + req = extra[0] + } + + var txnID string + if len(req.TransactionID) > 0 { + txnID = req.TransactionID + } else { + txnID = cli.TxnID() + } + + queryParams := map[string]string{} + if req.Timestamp > 0 { + queryParams["ts"] = strconv.FormatInt(req.Timestamp, 10) + } + + urlData := URLPath{"rooms", roomID, "send", eventType.String(), txnID} + if len(req.ParentID) > 0 { + urlData = URLPath{"rooms", roomID, "send_relation", req.ParentID, req.RelType, eventType.String(), txnID} + } + + urlPath := cli.BuildURLWithQuery(urlData, queryParams) + _, 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 id.RoomID, eventType event.Type, 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 id.RoomID, eventType event.Type, stateKey string, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) { + urlPath := cli.BuildURLWithQuery(URLPath{"rooms", roomID, "state", eventType.String(), stateKey}, map[string]string{ + "ts": strconv.FormatInt(ts, 10), + }) + _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp) + return +} + +// 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 id.RoomID, text string) (*RespSendEvent, error) { + return cli.SendMessageEvent(roomID, event.EventMessage, &event.MessageEventContent{ + MsgType: event.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 id.RoomID, body string, url id.ContentURI) (*RespSendEvent, error) { + return cli.SendMessageEvent(roomID, event.EventMessage, &event.MessageEventContent{ + MsgType: event.MsgImage, + Body: body, + URL: url.CUString(), + }) +} + +// 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 id.RoomID, body string, url id.ContentURI) (*RespSendEvent, error) { + return cli.SendMessageEvent(roomID, event.EventMessage, &event.MessageEventContent{ + MsgType: event.MsgVideo, + Body: body, + URL: url.CUString(), + }) +} + +// 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 id.RoomID, text string) (*RespSendEvent, error) { + return cli.SendMessageEvent(roomID, event.EventMessage, &event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: text, + }) +} + +func (cli *Client) SendReaction(roomID id.RoomID, eventID id.EventID, reaction string) (*RespSendEvent, error) { + return cli.SendMessageEvent(roomID, event.EventReaction, &event.ReactionEventContent{ + RelatesTo: event.RelatesTo{ + EventID: eventID, + Type: event.RelAnnotation, + Key: reaction, + }, + }) +} + +// 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 +func (cli *Client) RedactEvent(roomID id.RoomID, eventID id.EventID, extra ...ReqRedact) (resp *RespSendEvent, err error) { + req := ReqRedact{} + if len(extra) > 0 { + req = extra[0] + } + var txnID string + if len(req.TxnID) > 0 { + txnID = req.TxnID + } else { + txnID = cli.TxnID() + } + urlPath := cli.BuildURL("rooms", roomID, "redact", eventID, txnID) + _, err = cli.MakeRequest("PUT", urlPath, req, &resp) + return +} + +// CreateRoom creates a new Matrix room. See https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom +// resp, err := cli.CreateRoom(&mautrix.ReqCreateRoom{ +// Preset: "public_chat", +// }) +// fmt.Println("Room:", resp.RoomID) +func (cli *Client) CreateRoom(req *ReqCreateRoom) (resp *RespCreateRoom, err error) { + urlPath := cli.BuildURL("createRoom") + _, err = cli.MakeRequest("POST", urlPath, req, &resp) + return +} + +// LeaveRoom leaves the given room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave +func (cli *Client) LeaveRoom(roomID id.RoomID) (resp *RespLeaveRoom, err error) { + u := cli.BuildURL("rooms", roomID, "leave") + _, err = cli.MakeRequest("POST", u, struct{}{}, &resp) + return +} + +// ForgetRoom forgets a room entirely. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget +func (cli *Client) ForgetRoom(roomID id.RoomID) (resp *RespForgetRoom, err error) { + u := cli.BuildURL("rooms", roomID, "forget") + _, err = cli.MakeRequest("POST", u, struct{}{}, &resp) + return +} + +// InviteUser invites a user to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite +func (cli *Client) InviteUser(roomID id.RoomID, req *ReqInviteUser) (resp *RespInviteUser, err error) { + u := cli.BuildURL("rooms", roomID, "invite") + _, err = cli.MakeRequest("POST", u, req, &resp) + return +} + +// InviteUserByThirdParty invites a third-party identifier to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#invite-by-third-party-id-endpoint +func (cli *Client) InviteUserByThirdParty(roomID id.RoomID, req *ReqInvite3PID) (resp *RespInviteUser, err error) { + u := cli.BuildURL("rooms", roomID, "invite") + _, err = cli.MakeRequest("POST", u, req, &resp) + return +} + +// KickUser kicks a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick +func (cli *Client) KickUser(roomID id.RoomID, req *ReqKickUser) (resp *RespKickUser, err error) { + u := cli.BuildURL("rooms", roomID, "kick") + _, err = cli.MakeRequest("POST", u, req, &resp) + return +} + +// BanUser bans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban +func (cli *Client) BanUser(roomID id.RoomID, req *ReqBanUser) (resp *RespBanUser, err error) { + u := cli.BuildURL("rooms", roomID, "ban") + _, err = cli.MakeRequest("POST", u, req, &resp) + return +} + +// UnbanUser unbans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban +func (cli *Client) UnbanUser(roomID id.RoomID, req *ReqUnbanUser) (resp *RespUnbanUser, err error) { + u := cli.BuildURL("rooms", roomID, "unban") + _, err = cli.MakeRequest("POST", u, req, &resp) + return +} + +// UserTyping sets the typing status of the user. See https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid +func (cli *Client) UserTyping(roomID id.RoomID, typing bool, timeout int64) (resp *RespTyping, err error) { + req := ReqTyping{Typing: typing, Timeout: timeout} + u := cli.BuildURL("rooms", roomID, "typing", cli.UserID) + _, err = cli.MakeRequest("PUT", u, req, &resp) + return +} + +// GetPresence gets the presence of the user with the specified MXID. See https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-presence-userid-status +func (cli *Client) GetPresence(userID id.UserID) (resp *RespPresence, err error) { + resp = new(RespPresence) + u := cli.BuildURL("presence", userID, "status") + _, err = cli.MakeRequest("GET", u, nil, resp) + return +} + +// GetOwnPresence gets the user's presence. See https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-presence-userid-status +func (cli *Client) GetOwnPresence() (resp *RespPresence, err error) { + return cli.GetPresence(cli.UserID) +} + +func (cli *Client) SetPresence(status event.Presence) (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 id.RoomID, eventType event.Type, stateKey string, outContent interface{}) (err error) { + u := cli.BuildURL("rooms", roomID, "state", eventType.String(), stateKey) + _, err = cli.MakeRequest("GET", u, nil, outContent) + return +} + +// UploadLink uploads an HTTP URL and then returns an MXC URI. +func (cli *Client) UploadLink(link string) (*RespMediaUpload, error) { + res, err := cli.Client.Get(link) + if res != nil { + defer res.Body.Close() + } + if err != nil { + return nil, err + } + return cli.Upload(res.Body, res.Header.Get("Content-Type"), res.ContentLength) +} + +func (cli *Client) GetDownloadURL(mxcURL id.ContentURI) string { + return cli.BuildBaseURL("_matrix", "media", "r0", "download", mxcURL.Homeserver, mxcURL.FileID) +} + +func (cli *Client) Download(mxcURL id.ContentURI) (io.ReadCloser, error) { + resp, err := cli.Client.Get(cli.GetDownloadURL(mxcURL)) + if err != nil { + return nil, err + } + return resp.Body, nil +} + +func (cli *Client) DownloadBytes(mxcURL id.ContentURI) ([]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.UploadBytesWithName(data, contentType, "") +} + +func (cli *Client) UploadBytesWithName(data []byte, contentType, fileName string) (*RespMediaUpload, error) { + return cli.UploadMedia(ReqUploadMedia{ + Content: bytes.NewReader(data), + ContentLength: int64(len(data)), + ContentType: contentType, + FileName: fileName, + }) +} + +// Upload uploads the given data to the content repository and returns an MXC URI. +// +// Deprecated: UploadMedia should be used instead. +func (cli *Client) Upload(content io.Reader, contentType string, contentLength int64) (*RespMediaUpload, error) { + return cli.UploadMedia(ReqUploadMedia{ + Content: content, + ContentLength: contentLength, + ContentType: contentType, + }) +} + +type ReqUploadMedia struct { + Content io.Reader + ContentLength int64 + ContentType string + FileName string +} + +// UploadMedia uploads the given data 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) UploadMedia(data ReqUploadMedia) (*RespMediaUpload, error) { + u, _ := url.Parse(cli.BuildBaseURL("_matrix", "media", "r0", "upload")) + if len(data.FileName) > 0 { + q := u.Query() + q.Set("filename", data.FileName) + u.RawQuery = q.Encode() + } + + var headers http.Header + if len(data.ContentType) > 0 { + headers = http.Header{"Content-Type": []string{data.ContentType}} + } + + var m RespMediaUpload + _, err := cli.MakeFullRequest(FullRequest{ + Method: http.MethodPost, + URL: u.String(), + Headers: headers, + RequestBody: data.Content, + RequestLength: data.ContentLength, + ResponseJSON: &m, + }) + return &m, err +} + +// JoinedMembers returns a map of joined room members. See https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-joined-rooms +// +// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes. +// This API is primarily designed for application services which may want to efficiently look up joined members in a room. +func (cli *Client) JoinedMembers(roomID id.RoomID) (resp *RespJoinedMembers, err error) { + u := cli.BuildURL("rooms", roomID, "joined_members") + _, err = cli.MakeRequest("GET", u, nil, &resp) + return +} + +func (cli *Client) Members(roomID id.RoomID, req ...ReqMembers) (resp *RespMembers, err error) { + var extra ReqMembers + if len(req) > 0 { + extra = req[0] + } + query := map[string]string{} + if len(extra.At) > 0 { + query["at"] = extra.At + } + if len(extra.Membership) > 0 { + query["membership"] = string(extra.Membership) + } + if len(extra.NotMembership) > 0 { + query["not_membership"] = string(extra.NotMembership) + } + u := cli.BuildURLWithQuery(URLPath{"rooms", roomID, "members"}, query) + _, err = cli.MakeRequest("GET", u, nil, &resp) + return +} + +// JoinedRooms returns a list of rooms which the client is joined to. See https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-joined-rooms +// +// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes. +// This API is primarily designed for application services which may want to efficiently look up joined rooms. +func (cli *Client) JoinedRooms() (resp *RespJoinedRooms, err error) { + u := cli.BuildURL("joined_rooms") + _, err = cli.MakeRequest("GET", u, nil, &resp) + return +} + +// Messages returns a list of message and state events for a room. It uses +// pagination query parameters to paginate history in the room. +// See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages +func (cli *Client) Messages(roomID id.RoomID, from, to string, dir rune, limit int) (resp *RespMessages, err error) { + filter := cli.Syncer.GetFilterJSON(cli.UserID) + filterJSON, err := json.Marshal(filter) + if err != nil { + return nil, err + } + query := map[string]string{ + "from": from, + "dir": string(dir), + "filter": string(filterJSON), + } + if to != "" { + query["to"] = to + } + if limit != 0 { + query["limit"] = strconv.Itoa(limit) + } + + urlPath := cli.BuildURLWithQuery(URLPath{"rooms", roomID, "messages"}, query) + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) GetEvent(roomID id.RoomID, eventID id.EventID) (resp *event.Event, err error) { + urlPath := cli.BuildURL("rooms", roomID, "event", eventID) + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) MarkRead(roomID id.RoomID, eventID id.EventID) (err error) { + return cli.MarkReadWithContent(roomID, eventID, struct{}{}) +} + +// MarkReadWithContent sends a read receipt including custom data. +// N.B. This is not (yet) a part of the spec, normal servers will drop any extra content. +func (cli *Client) MarkReadWithContent(roomID id.RoomID, eventID id.EventID, content interface{}) (err error) { + urlPath := cli.BuildURL("rooms", roomID, "receipt", "m.read", eventID) + _, err = cli.MakeRequest("POST", urlPath, &content, nil) + return +} + +func (cli *Client) AddTag(roomID id.RoomID, tag string, order float64) error { + var tagData event.Tag + if order == order { + tagData.Order = json.Number(strconv.FormatFloat(order, 'e', -1, 64)) + } + return cli.AddTagWithCustomData(roomID, tag, tagData) +} + +func (cli *Client) AddTagWithCustomData(roomID id.RoomID, tag string, data interface{}) (err error) { + urlPath := cli.BuildURL("user", cli.UserID, "rooms", roomID, "tags", tag) + _, err = cli.MakeRequest("PUT", urlPath, data, nil) + return +} + +func (cli *Client) GetTags(roomID id.RoomID) (tags event.TagEventContent, err error) { + err = cli.GetTagsWithCustomData(roomID, &tags) + return +} + +func (cli *Client) GetTagsWithCustomData(roomID id.RoomID, resp interface{}) (err error) { + urlPath := cli.BuildURL("user", cli.UserID, "rooms", roomID, "tags") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) RemoveTag(roomID id.RoomID, tag string) (err error) { + urlPath := cli.BuildURL("user", cli.UserID, "rooms", roomID, "tags", tag) + _, err = cli.MakeRequest("DELETE", urlPath, nil, nil) + return +} + +// Deprecated: Synapse may not handle setting m.tag directly properly, so you should use the Add/RemoveTag methods instead. +func (cli *Client) SetTags(roomID id.RoomID, tags event.Tags) (err error) { + return cli.SetRoomAccountData(roomID, "m.tag", map[string]event.Tags{ + "tags": tags, + }) +} + +// 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) { + urlPath := cli.BuildURL("voip", "turnServer") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) CreateAlias(alias id.RoomAlias, roomID id.RoomID) (resp *RespAliasCreate, err error) { + urlPath := cli.BuildURL("directory", "room", alias) + _, err = cli.MakeRequest("PUT", urlPath, &ReqAliasCreate{RoomID: roomID}, &resp) + return +} + +func (cli *Client) ResolveAlias(alias id.RoomAlias) (resp *RespAliasResolve, err error) { + urlPath := cli.BuildURL("directory", "room", alias) + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) DeleteAlias(alias id.RoomAlias) (resp *RespAliasDelete, err error) { + urlPath := cli.BuildURL("directory", "room", alias) + _, err = cli.MakeRequest("DELETE", urlPath, nil, &resp) + return +} + +func (cli *Client) UploadKeys(req *ReqUploadKeys) (resp *RespUploadKeys, err error) { + urlPath := cli.BuildURL("keys", "upload") + _, err = cli.MakeRequest("POST", urlPath, req, &resp) + return +} + +func (cli *Client) QueryKeys(req *ReqQueryKeys) (resp *RespQueryKeys, err error) { + urlPath := cli.BuildURL("keys", "query") + _, err = cli.MakeRequest("POST", urlPath, req, &resp) + return +} + +func (cli *Client) ClaimKeys(req *ReqClaimKeys) (resp *RespClaimKeys, err error) { + urlPath := cli.BuildURL("keys", "claim") + _, err = cli.MakeRequest("POST", urlPath, req, &resp) + return +} + +func (cli *Client) GetKeyChanges(from, to string) (resp *RespKeyChanges, err error) { + urlPath := cli.BuildURLWithQuery(URLPath{"keys", "changes"}, map[string]string{ + "from": from, + "to": to, + }) + _, err = cli.MakeRequest("POST", urlPath, nil, &resp) + return +} + +func (cli *Client) SendToDevice(eventType event.Type, req *ReqSendToDevice) (resp *RespSendToDevice, err error) { + urlPath := cli.BuildURL("sendToDevice", eventType.String(), cli.TxnID()) + _, err = cli.MakeRequest("PUT", urlPath, req, &resp) + return +} + +func (cli *Client) GetDevicesInfo() (resp *RespDevicesInfo, err error) { + urlPath := cli.BuildURL("devices") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) GetDeviceInfo(deviceID id.DeviceID) (resp *RespDeviceInfo, err error) { + urlPath := cli.BuildURL("devices", deviceID) + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) SetDeviceInfo(deviceID id.DeviceID, req *ReqDeviceInfo) error { + urlPath := cli.BuildURL("devices", deviceID) + _, err := cli.MakeRequest("PUT", urlPath, req, nil) + return err +} + +func (cli *Client) DeleteDevice(deviceID id.DeviceID, req *ReqDeleteDevice) error { + urlPath := cli.BuildURL("devices", deviceID) + _, err := cli.MakeRequest("DELETE", urlPath, req, nil) + return err +} + +func (cli *Client) DeleteDevices(req *ReqDeleteDevices) error { + urlPath := cli.BuildURL("delete_devices") + _, err := cli.MakeRequest("DELETE", urlPath, req, nil) + return err +} + +type UIACallback = func(*RespUserInteractive) interface{} + +// UploadCrossSigningKeys uploads the given cross-signing keys to the server. +// Because the endpoint requires user-interactive authentication a callback must be provided that, +// given the UI auth parameters, produces the required result (or nil to end the flow). +func (cli *Client) UploadCrossSigningKeys(keys *UploadCrossSigningKeysReq, uiaCallback UIACallback) error { + urlPath := cli.BuildBaseURL("_matrix", "client", "unstable", "keys", "device_signing", "upload") + content, err := cli.MakeRequest("POST", urlPath, keys, nil) + if respErr, ok := err.(HTTPError); ok && respErr.IsStatus(http.StatusUnauthorized) { + // try again with UI auth + var uiAuthResp RespUserInteractive + if err := json.Unmarshal(content, &uiAuthResp); err != nil { + return fmt.Errorf("failed to decode UIA response: %w", err) + } + auth := uiaCallback(&uiAuthResp) + if auth != nil { + keys.Auth = auth + return cli.UploadCrossSigningKeys(keys, uiaCallback) + } + } + return err +} + +func (cli *Client) UploadSignatures(req *ReqUploadSignatures) (resp *RespUploadSignatures, err error) { + urlPath := cli.BuildBaseURL("_matrix", "client", "unstable", "keys", "signatures", "upload") + _, err = cli.MakeRequest("POST", urlPath, req, &resp) + return +} + +// GetPushRules returns the push notification rules for the global scope. +func (cli *Client) GetPushRules() (*pushrules.PushRuleset, error) { + return cli.GetScopedPushRules("global") +} + +// GetScopedPushRules returns the push notification rules for the given scope. +func (cli *Client) GetScopedPushRules(scope string) (resp *pushrules.PushRuleset, err error) { + u, _ := url.Parse(cli.BuildURL("pushrules", scope)) + // client.BuildURL returns the URL without a trailing slash, but the pushrules endpoint requires the slash. + u.Path += "/" + _, err = cli.MakeRequest("GET", u.String(), nil, &resp) + return +} + +func (cli *Client) GetPushRule(scope string, kind pushrules.PushRuleType, ruleID string) (resp *pushrules.PushRule, err error) { + urlPath := cli.BuildURL("pushrules", scope, kind, ruleID) + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + if resp != nil { + resp.Type = kind + } + return +} + +func (cli *Client) DeletePushRule(scope string, kind pushrules.PushRuleType, ruleID string) error { + urlPath := cli.BuildURL("pushrules", scope, kind, ruleID) + _, err := cli.MakeRequest("DELETE", urlPath, nil, nil) + return err +} + +func (cli *Client) PutPushRule(scope string, kind pushrules.PushRuleType, ruleID string, req *ReqPutPushRule) error { + query := make(map[string]string) + if len(req.After) > 0 { + query["after"] = req.After + } + if len(req.Before) > 0 { + query["before"] = req.Before + } + urlPath := cli.BuildURLWithQuery(URLPath{"pushrules", scope, kind, ruleID}, query) + _, err := cli.MakeRequest("PUT", urlPath, req, nil) + return err +} + +// TxnID returns the next transaction ID. +func (cli *Client) TxnID() string { + txnID := atomic.AddInt32(&cli.txnID, 1) + return fmt.Sprintf("mautrix-go_%d_%d", time.Now().UnixNano(), txnID) +} + +// NewClient creates a new Matrix Client ready for syncing +func NewClient(homeserverURL string, userID id.UserID, accessToken string) (*Client, error) { + hsURL, err := url.Parse(homeserverURL) + if err != nil { + return nil, err + } + if hsURL.Scheme == "" { + hsURL.Scheme = "https" + } + return &Client{ + AccessToken: accessToken, + UserAgent: DefaultUserAgent, + HomeserverURL: hsURL, + UserID: userID, + Client: &http.Client{Timeout: 180 * time.Second}, + Prefix: URLPath{"_matrix", "client", "r0"}, + Syncer: NewDefaultSyncer(), + // By default, use an in-memory store which will never save filter ids / next batch tokens to disk. + // The client will work with this storer: it just won't remember across restarts. + // In practice, a database backend should be used. + Store: NewInMemoryStore(), + }, nil +} diff --git a/vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go b/vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go new file mode 100644 index 00000000..170c55c9 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go @@ -0,0 +1,171 @@ +// 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 attachment + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "encoding/base64" + "errors" + "hash" + "io" + + "maunium.net/go/mautrix/crypto/utils" +) + +var ( + HashMismatch = errors.New("mismatching SHA-256 digest") + UnsupportedVersion = errors.New("unsupported Matrix file encryption version") + UnsupportedAlgorithm = errors.New("unsupported JWK encryption algorithm") + InvalidKey = errors.New("failed to decode key") + InvalidInitVector = errors.New("failed to decode initialization vector") + ReaderClosed = errors.New("encrypting reader was already closed") +) + +var ( + keyBase64Length = base64.RawURLEncoding.EncodedLen(utils.AESCTRKeyLength) + ivBase64Length = base64.RawStdEncoding.EncodedLen(utils.AESCTRIVLength) + hashBase64Length = base64.RawStdEncoding.EncodedLen(utils.SHAHashLength) +) + +type JSONWebKey struct { + Key string `json:"k"` + Algorithm string `json:"alg"` + Extractable bool `json:"ext"` + KeyType string `json:"kty"` + KeyOps []string `json:"key_ops"` +} + +type EncryptedFileHashes struct { + SHA256 string `json:"sha256"` +} + +type decodedKeys struct { + key [utils.AESCTRKeyLength]byte + iv [utils.AESCTRIVLength]byte +} + +type EncryptedFile struct { + Key JSONWebKey `json:"key"` + InitVector string `json:"iv"` + Hashes EncryptedFileHashes `json:"hashes"` + Version string `json:"v"` + + decoded *decodedKeys `json:"-"` +} + +func NewEncryptedFile() *EncryptedFile { + key, iv := utils.GenAttachmentA256CTR() + return &EncryptedFile{ + Key: JSONWebKey{ + Key: base64.RawURLEncoding.EncodeToString(key[:]), + Algorithm: "A256CTR", + Extractable: true, + KeyType: "oct", + KeyOps: []string{"encrypt", "decrypt"}, + }, + InitVector: base64.RawStdEncoding.EncodeToString(iv[:]), + Version: "v2", + + decoded: &decodedKeys{key, iv}, + } +} + +func (ef *EncryptedFile) decodeKeys() error { + if ef.decoded != nil { + return nil + } else if len(ef.Key.Key) != keyBase64Length { + return InvalidKey + } else if len(ef.InitVector) != ivBase64Length { + return InvalidInitVector + } + ef.decoded = &decodedKeys{} + _, err := base64.RawURLEncoding.Decode(ef.decoded.key[:], []byte(ef.Key.Key)) + if err != nil { + return InvalidKey + } + _, err = base64.RawStdEncoding.Decode(ef.decoded.iv[:], []byte(ef.InitVector)) + if err != nil { + return InvalidInitVector + } + return nil +} + +func (ef *EncryptedFile) Encrypt(plaintext []byte) []byte { + ef.decodeKeys() + ciphertext := utils.XorA256CTR(plaintext, ef.decoded.key, ef.decoded.iv) + checksum := sha256.Sum256(ciphertext) + ef.Hashes.SHA256 = base64.RawStdEncoding.EncodeToString(checksum[:]) + return ciphertext +} + +// encryptingReader is a variation of cipher.StreamReader that also hashes the content. +type encryptingReader struct { + stream cipher.Stream + hash hash.Hash + source io.Reader + file *EncryptedFile + closed bool +} + +func (r *encryptingReader) Read(dst []byte) (n int, err error) { + if r.closed { + return 0, ReaderClosed + } + n, err = r.source.Read(dst) + r.stream.XORKeyStream(dst[:n], dst[:n]) + r.hash.Write(dst[:n]) + return +} + +func (r *encryptingReader) Close() (err error) { + closer, ok := r.source.(io.ReadCloser) + if ok { + err = closer.Close() + } + r.file.Hashes.SHA256 = base64.RawStdEncoding.EncodeToString(r.hash.Sum(nil)) + r.closed = true + return +} + +func (ef *EncryptedFile) EncryptStream(reader io.Reader) io.ReadCloser { + ef.decodeKeys() + block, _ := aes.NewCipher(ef.decoded.key[:]) + return &encryptingReader{ + stream: cipher.NewCTR(block, ef.decoded.iv[:]), + hash: sha256.New(), + source: reader, + file: ef, + } +} + +func (ef *EncryptedFile) checkHash(ciphertext []byte) bool { + if len(ef.Hashes.SHA256) != hashBase64Length { + return false + } + var checksum [utils.SHAHashLength]byte + _, err := base64.RawStdEncoding.Decode(checksum[:], []byte(ef.Hashes.SHA256)) + if err != nil { + return false + } + return checksum == sha256.Sum256(ciphertext) +} + +func (ef *EncryptedFile) Decrypt(ciphertext []byte) ([]byte, error) { + if ef.Version != "v2" { + return nil, UnsupportedVersion + } else if ef.Key.Algorithm != "A256CTR" { + return nil, UnsupportedAlgorithm + } else if !ef.checkHash(ciphertext) { + return nil, HashMismatch + } else if err := ef.decodeKeys(); err != nil { + return nil, err + } else { + return utils.XorA256CTR(ciphertext, ef.decoded.key, ef.decoded.iv), nil + } +} diff --git a/vendor/maunium.net/go/mautrix/crypto/utils/utils.go b/vendor/maunium.net/go/mautrix/crypto/utils/utils.go new file mode 100644 index 00000000..652b2d33 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/crypto/utils/utils.go @@ -0,0 +1,133 @@ +// Copyright (c) 2020 Nikos Filippakis +// +// 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 utils + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/sha256" + "crypto/sha512" + "encoding/base64" + "math/rand" + "strings" + + "github.com/btcsuite/btcutil/base58" + "golang.org/x/crypto/hkdf" + "golang.org/x/crypto/pbkdf2" +) + +const ( + // AESCTRKeyLength is the length of the AES256-CTR key used. + AESCTRKeyLength = 32 + // AESCTRIVLength is the length of the AES256-CTR IV used. + AESCTRIVLength = 16 + // HMACKeyLength is the length of the HMAC key used. + HMACKeyLength = 32 + // SHAHashLength is the length of the SHA hash used. + SHAHashLength = 32 +) + +// XorA256CTR encrypts the input with the keystream generated by the AES256-CTR algorithm with the given arguments. +func XorA256CTR(source []byte, key [AESCTRKeyLength]byte, iv [AESCTRIVLength]byte) []byte { + block, _ := aes.NewCipher(key[:]) + result := make([]byte, len(source)) + cipher.NewCTR(block, iv[:]).XORKeyStream(result, source) + return result +} + +// GenAttachmentA256CTR generates a new random AES256-CTR key and IV suitable for encrypting attachments. +func GenAttachmentA256CTR() (key [AESCTRKeyLength]byte, iv [AESCTRIVLength]byte) { + _, err := rand.Read(key[:]) + if err != nil { + panic(err) + } + + // The last 8 bytes of the IV act as the counter in AES-CTR, which means they're left empty here + _, err = rand.Read(iv[:8]) + if err != nil { + panic(err) + } + return +} + +// GenA256CTRIV generates a random IV for AES256-CTR with the last bit set to zero. +func GenA256CTRIV() (iv [AESCTRIVLength]byte) { + _, err := rand.Read(iv[:]) + if err != nil { + panic(err) + } + iv[8] &= 0x7F + return +} + +// DeriveKeysSHA256 derives an AES and a HMAC key from the given recovery key. +func DeriveKeysSHA256(key []byte, name string) ([AESCTRKeyLength]byte, [HMACKeyLength]byte) { + var zeroBytes [32]byte + + derivedHkdf := hkdf.New(sha256.New, key[:], zeroBytes[:], []byte(name)) + + var aesKey [AESCTRKeyLength]byte + var hmacKey [HMACKeyLength]byte + derivedHkdf.Read(aesKey[:]) + derivedHkdf.Read(hmacKey[:]) + + return aesKey, hmacKey +} + +// PBKDF2SHA512 generates a key of the given bit-length using the given passphrase, salt and iteration count. +func PBKDF2SHA512(password []byte, salt []byte, iters int, keyLenBits int) []byte { + return pbkdf2.Key(password, salt, iters, keyLenBits/8, sha512.New) +} + +// DecodeBase58RecoveryKey recovers the secret storage from a recovery key. +func DecodeBase58RecoveryKey(recoveryKey string) []byte { + noSpaces := strings.ReplaceAll(recoveryKey, " ", "") + decoded := base58.Decode(noSpaces) + if len(decoded) != AESCTRKeyLength+3 { // AESCTRKeyLength bytes key and 3 bytes prefix / parity + return nil + } + var parity byte + for _, b := range decoded[:34] { + parity ^= b + } + if parity != decoded[34] || decoded[0] != 0x8B || decoded[1] != 1 { + return nil + } + return decoded[2:34] +} + +// EncodeBase58RecoveryKey recovers the secret storage from a recovery key. +func EncodeBase58RecoveryKey(key []byte) string { + var inputBytes [35]byte + copy(inputBytes[2:34], key[:]) + inputBytes[0] = 0x8B + inputBytes[1] = 1 + + var parity byte + for _, b := range inputBytes[:34] { + parity ^= b + } + inputBytes[34] = parity + recoveryKey := base58.Encode(inputBytes[:]) + + var spacedKey string + for i, c := range recoveryKey { + if i > 0 && i%4 == 0 { + spacedKey += " " + } + spacedKey += string(c) + } + return spacedKey +} + +// HMACSHA256B64 calculates the base64 of the SHA256 hmac of the input with the given key. +func HMACSHA256B64(input []byte, hmacKey [HMACKeyLength]byte) string { + h := hmac.New(sha256.New, hmacKey[:]) + h.Write(input) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} diff --git a/vendor/maunium.net/go/mautrix/error.go b/vendor/maunium.net/go/mautrix/error.go new file mode 100644 index 00000000..52234d02 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/error.go @@ -0,0 +1,144 @@ +// 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 mautrix + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" +) + +// Common error codes from https://matrix.org/docs/spec/client_server/latest#api-standards +// +// Can be used with errors.Is() to check the response code without casting the error: +// err := client.Sync() +// if errors.Is(err, MUnknownToken) { +// // logout +// } +var ( + // Forbidden access, e.g. joining a room without permission, failed login. + MForbidden = RespError{ErrCode: "M_FORBIDDEN"} + // The access token specified was not recognised. + MUnknownToken = RespError{ErrCode: "M_UNKNOWN_TOKEN"} + // No access token was specified for the request. + MMissingToken = RespError{ErrCode: "M_MISSING_TOKEN"} + // Request contained valid JSON, but it was malformed in some way, e.g. missing required keys, invalid values for keys. + MBadJSON = RespError{ErrCode: "M_BAD_JSON"} + // Request did not contain valid JSON. + MNotJSON = RespError{ErrCode: "M_NOT_JSON"} + // No resource was found for this request. + MNotFound = RespError{ErrCode: "M_NOT_FOUND"} + // Too many requests have been sent in a short period of time. Wait a while then try again. + MLimitExceeded = RespError{ErrCode: "M_LIMIT_EXCEEDED"} + // The user ID associated with the request has been deactivated. + // Typically for endpoints that prove authentication, such as /login. + MUserDeactivated = RespError{ErrCode: "M_USER_DEACTIVATED"} + // Encountered when trying to register a user ID which has been taken. + MUserInUse = RespError{ErrCode: "M_USER_IN_USE"} + // Encountered when trying to register a user ID which is not valid. + MInvalidUsername = RespError{ErrCode: "M_INVALID_USERNAME"} + // Sent when the room alias given to the createRoom API is already in use. + MRoomInUse = RespError{ErrCode: "M_ROOM_IN_USE"} + // The state change requested cannot be performed, such as attempting to unban a user who is not banned. + MBadState = RespError{ErrCode: "M_BAD_STATE"} + // The request or entity was too large. + MTooLarge = RespError{ErrCode: "M_TOO_LARGE"} + // The resource being requested is reserved by an application service, or the application service making the request has not created the resource. + MExclusive = RespError{ErrCode: "M_EXCLUSIVE"} + // The client's request to create a room used a room version that the server does not support. + MUnsupportedRoomVersion = RespError{ErrCode: "M_UNSUPPORTED_ROOM_VERSION"} + // The client attempted to join a room that has a version the server does not support. + // Inspect the room_version property of the error response for the room's version. + MIncompatibleRoomVersion = RespError{ErrCode: "M_INCOMPATIBLE_ROOM_VERSION"} +) + +// HTTPError An HTTP Error response, which may wrap an underlying native Go Error. +type HTTPError struct { + Request *http.Request + Response *http.Response + ResponseBody string + + WrappedError error + RespError *RespError + Message string +} + +func (e HTTPError) Is(err error) bool { + return (e.RespError != nil && errors.Is(e.RespError, err)) || (e.WrappedError != nil && errors.Is(e.WrappedError, err)) +} + +func (e HTTPError) IsStatus(code int) bool { + return e.Response != nil && e.Response.StatusCode == code +} + +func (e HTTPError) Error() string { + if e.WrappedError != nil { + return fmt.Sprintf("%s: %v", e.Message, e.WrappedError) + } else if e.RespError != nil { + return fmt.Sprintf("failed to %s %s: %s (HTTP %d): %s", e.Request.Method, e.Request.URL.Path, + e.RespError.ErrCode, e.Response.StatusCode, e.RespError.Err) + } else { + msg := fmt.Sprintf("failed to %s %s: %s", e.Request.Method, e.Request.URL.Path, e.Response.Status) + if len(e.ResponseBody) > 0 { + msg = fmt.Sprintf("%s\n%s", msg, e.ResponseBody) + } + return msg + } +} + +func (e HTTPError) Unwrap() error { + if e.WrappedError != nil { + return e.WrappedError + } else if e.RespError != nil { + return *e.RespError + } + return nil +} + +// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface. +// See http://matrix.org/docs/spec/client_server/r0.6.1.html#api-standards +type RespError struct { + ErrCode string + Err string + ExtraData map[string]interface{} +} + +func (e *RespError) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &e.ExtraData) + if err != nil { + return err + } + e.ErrCode, _ = e.ExtraData["errcode"].(string) + e.Err, _ = e.ExtraData["error"].(string) + return nil +} + +func (e *RespError) MarshalJSON() ([]byte, error) { + if e.ExtraData == nil { + e.ExtraData = make(map[string]interface{}) + } + e.ExtraData["errcode"] = e.ErrCode + e.ExtraData["error"] = e.Err + return json.Marshal(&e.ExtraData) +} + +// Error returns the errcode and error message. +func (e RespError) Error() string { + return e.ErrCode + ": " + e.Err +} + +func (e RespError) Is(err error) bool { + e2, ok := err.(RespError) + if !ok { + return false + } + if e.ErrCode == "M_UNKNOWN" && e2.ErrCode == "M_UNKNOWN" { + return e.Err == e2.Err + } + return e2.ErrCode == e.ErrCode +} diff --git a/vendor/maunium.net/go/mautrix/event/accountdata.go b/vendor/maunium.net/go/mautrix/event/accountdata.go new file mode 100644 index 00000000..32932337 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/accountdata.go @@ -0,0 +1,45 @@ +// 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" + + "maunium.net/go/mautrix/id" +) + +// TagEventContent represents the content of a m.tag room account data event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-tag +type TagEventContent struct { + Tags Tags `json:"tags"` +} + +type Tags map[string]Tag + +type Tag struct { + Order json.Number `json:"order,omitempty"` +} + +// DirectChatsEventContent represents the content of a m.direct account data event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-direct +type DirectChatsEventContent map[id.UserID][]id.RoomID + +// FullyReadEventContent represents the content of a m.fully_read account data event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-fully-read +type FullyReadEventContent struct { + EventID id.EventID `json:"event_id"` +} + +// IgnoredUserListEventContent represents the content of a m.ignored_user_list account data event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-ignored-user-list +type IgnoredUserListEventContent struct { + IgnoredUsers map[id.UserID]IgnoredUser `json:"ignored_users"` +} + +type IgnoredUser struct { + // This is an empty object +} \ No newline at end of file diff --git a/vendor/maunium.net/go/mautrix/event/content.go b/vendor/maunium.net/go/mautrix/event/content.go new file mode 100644 index 00000000..42579ff1 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/content.go @@ -0,0 +1,457 @@ +// 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/gob" + "encoding/json" + "errors" + "fmt" + "reflect" +) + +// TypeMap is a mapping from event type to the content struct type. +// This is used by Content.ParseRaw() for creating the correct type of struct. +var TypeMap = map[Type]reflect.Type{ + StateMember: reflect.TypeOf(MemberEventContent{}), + StatePowerLevels: reflect.TypeOf(PowerLevelsEventContent{}), + StateCanonicalAlias: reflect.TypeOf(CanonicalAliasEventContent{}), + StateRoomName: reflect.TypeOf(RoomNameEventContent{}), + StateRoomAvatar: reflect.TypeOf(RoomAvatarEventContent{}), + StateTopic: reflect.TypeOf(TopicEventContent{}), + StateTombstone: reflect.TypeOf(TombstoneEventContent{}), + StateCreate: reflect.TypeOf(CreateEventContent{}), + StateJoinRules: reflect.TypeOf(JoinRulesEventContent{}), + StateHistoryVisibility: reflect.TypeOf(HistoryVisibilityEventContent{}), + StateGuestAccess: reflect.TypeOf(GuestAccessEventContent{}), + StatePinnedEvents: reflect.TypeOf(PinnedEventsEventContent{}), + StateEncryption: reflect.TypeOf(EncryptionEventContent{}), + + EventMessage: reflect.TypeOf(MessageEventContent{}), + EventSticker: reflect.TypeOf(MessageEventContent{}), + EventEncrypted: reflect.TypeOf(EncryptedEventContent{}), + EventRedaction: reflect.TypeOf(RedactionEventContent{}), + EventReaction: reflect.TypeOf(ReactionEventContent{}), + + AccountDataRoomTags: reflect.TypeOf(TagEventContent{}), + AccountDataDirectChats: reflect.TypeOf(DirectChatsEventContent{}), + AccountDataFullyRead: reflect.TypeOf(FullyReadEventContent{}), + AccountDataIgnoredUserList: reflect.TypeOf(IgnoredUserListEventContent{}), + + EphemeralEventTyping: reflect.TypeOf(TypingEventContent{}), + EphemeralEventReceipt: reflect.TypeOf(ReceiptEventContent{}), + EphemeralEventPresence: reflect.TypeOf(PresenceEventContent{}), + + InRoomVerificationStart: reflect.TypeOf(VerificationStartEventContent{}), + InRoomVerificationReady: reflect.TypeOf(VerificationReadyEventContent{}), + InRoomVerificationAccept: reflect.TypeOf(VerificationAcceptEventContent{}), + InRoomVerificationKey: reflect.TypeOf(VerificationKeyEventContent{}), + InRoomVerificationMAC: reflect.TypeOf(VerificationMacEventContent{}), + InRoomVerificationCancel: reflect.TypeOf(VerificationCancelEventContent{}), + + ToDeviceRoomKey: reflect.TypeOf(RoomKeyEventContent{}), + ToDeviceForwardedRoomKey: reflect.TypeOf(ForwardedRoomKeyEventContent{}), + ToDeviceRoomKeyRequest: reflect.TypeOf(RoomKeyRequestEventContent{}), + ToDeviceEncrypted: reflect.TypeOf(EncryptedEventContent{}), + ToDeviceRoomKeyWithheld: reflect.TypeOf(RoomKeyWithheldEventContent{}), + + ToDeviceVerificationStart: reflect.TypeOf(VerificationStartEventContent{}), + ToDeviceVerificationAccept: reflect.TypeOf(VerificationAcceptEventContent{}), + ToDeviceVerificationKey: reflect.TypeOf(VerificationKeyEventContent{}), + ToDeviceVerificationMAC: reflect.TypeOf(VerificationMacEventContent{}), + ToDeviceVerificationCancel: reflect.TypeOf(VerificationCancelEventContent{}), + ToDeviceVerificationRequest: reflect.TypeOf(VerificationRequestEventContent{}), + + ToDeviceOrgMatrixRoomKeyWithheld: reflect.TypeOf(RoomKeyWithheldEventContent{}), + + CallInvite: reflect.TypeOf(CallInviteEventContent{}), + CallCandidates: reflect.TypeOf(CallCandidatesEventContent{}), + CallAnswer: reflect.TypeOf(CallAnswerEventContent{}), + CallReject: reflect.TypeOf(CallRejectEventContent{}), + CallSelectAnswer: reflect.TypeOf(CallSelectAnswerEventContent{}), + CallNegotiate: reflect.TypeOf(CallNegotiateEventContent{}), + CallHangup: reflect.TypeOf(CallHangupEventContent{}), +} + +// Content stores the content of a Matrix event. +// +// By default, the content is only parsed into a map[string]interface{}. However, you can call ParseRaw with the +// correct event type to parse the content into a nicer struct, which you can then access from Parsed or via the +// helper functions. +type Content struct { + VeryRaw json.RawMessage + Raw map[string]interface{} + Parsed interface{} +} + +type Relatable interface { + GetRelatesTo() *RelatesTo + OptionalGetRelatesTo() *RelatesTo + SetRelatesTo(rel *RelatesTo) +} + +func (content *Content) UnmarshalJSON(data []byte) error { + content.VeryRaw = data + err := json.Unmarshal(data, &content.Raw) + return err +} + +func (content *Content) MarshalJSON() ([]byte, error) { + if content.Raw == nil { + if content.Parsed == nil { + if content.VeryRaw == nil { + return []byte("{}"), nil + } + return content.VeryRaw, nil + } + return json.Marshal(content.Parsed) + } else if content.Parsed != nil { + // TODO this whole thing is incredibly hacky + // It needs to produce JSON, where: + // * content.Parsed is applied after content.Raw + // * MarshalJSON() is respected inside content.Parsed + // * Custom field inside nested objects of content.Raw are preserved, + // even if content.Parsed contains the higher-level objects. + // * content.Raw is not modified + + unparsed, err := json.Marshal(content.Parsed) + if err != nil { + return nil, err + } + + var rawParsed map[string]interface{} + err = json.Unmarshal(unparsed, &rawParsed) + if err != nil { + return nil, err + } + + output := make(map[string]interface{}) + for key, value := range content.Raw { + output[key] = value + } + + mergeMaps(output, rawParsed) + return json.Marshal(output) + } + return json.Marshal(content.Raw) +} + +func IsUnsupportedContentType(err error) bool { + return errors.Is(err, UnsupportedContentType) +} + +var ContentAlreadyParsed = errors.New("content is already parsed") +var UnsupportedContentType = errors.New("unsupported event type") + +func (content *Content) ParseRaw(evtType Type) error { + if content.Parsed != nil { + return ContentAlreadyParsed + } + structType, ok := TypeMap[evtType] + if !ok { + return fmt.Errorf("%w %s", UnsupportedContentType, evtType.Repr()) + } + content.Parsed = reflect.New(structType).Interface() + return json.Unmarshal(content.VeryRaw, &content.Parsed) +} + +func mergeMaps(into, from map[string]interface{}) { + for key, newValue := range from { + existingValue, ok := into[key] + if !ok { + into[key] = newValue + continue + } + existingValueMap, okEx := existingValue.(map[string]interface{}) + newValueMap, okNew := newValue.(map[string]interface{}) + if okEx && okNew { + mergeMaps(existingValueMap, newValueMap) + } else { + into[key] = newValue + } + } +} + +func init() { + gob.Register(&MemberEventContent{}) + gob.Register(&PowerLevelsEventContent{}) + gob.Register(&CanonicalAliasEventContent{}) + gob.Register(&EncryptionEventContent{}) + gob.Register(&RoomNameEventContent{}) + gob.Register(&RoomAvatarEventContent{}) + gob.Register(&TopicEventContent{}) + gob.Register(&TombstoneEventContent{}) + gob.Register(&CreateEventContent{}) + gob.Register(&JoinRulesEventContent{}) + gob.Register(&HistoryVisibilityEventContent{}) + gob.Register(&GuestAccessEventContent{}) + gob.Register(&PinnedEventsEventContent{}) + gob.Register(&MessageEventContent{}) + gob.Register(&MessageEventContent{}) + gob.Register(&EncryptedEventContent{}) + gob.Register(&RedactionEventContent{}) + gob.Register(&ReactionEventContent{}) + gob.Register(&TagEventContent{}) + gob.Register(&DirectChatsEventContent{}) + gob.Register(&FullyReadEventContent{}) + gob.Register(&IgnoredUserListEventContent{}) + gob.Register(&TypingEventContent{}) + gob.Register(&ReceiptEventContent{}) + gob.Register(&PresenceEventContent{}) + gob.Register(&RoomKeyEventContent{}) + gob.Register(&ForwardedRoomKeyEventContent{}) + gob.Register(&RoomKeyRequestEventContent{}) + gob.Register(&RoomKeyWithheldEventContent{}) +} + +// Helper cast functions below + +func (content *Content) AsMember() *MemberEventContent { + casted, ok := content.Parsed.(*MemberEventContent) + if !ok { + return &MemberEventContent{} + } + return casted +} +func (content *Content) AsPowerLevels() *PowerLevelsEventContent { + casted, ok := content.Parsed.(*PowerLevelsEventContent) + if !ok { + return &PowerLevelsEventContent{} + } + return casted +} +func (content *Content) AsCanonicalAlias() *CanonicalAliasEventContent { + casted, ok := content.Parsed.(*CanonicalAliasEventContent) + if !ok { + return &CanonicalAliasEventContent{} + } + return casted +} +func (content *Content) AsRoomName() *RoomNameEventContent { + casted, ok := content.Parsed.(*RoomNameEventContent) + if !ok { + return &RoomNameEventContent{} + } + return casted +} +func (content *Content) AsRoomAvatar() *RoomAvatarEventContent { + casted, ok := content.Parsed.(*RoomAvatarEventContent) + if !ok { + return &RoomAvatarEventContent{} + } + return casted +} +func (content *Content) AsTopic() *TopicEventContent { + casted, ok := content.Parsed.(*TopicEventContent) + if !ok { + return &TopicEventContent{} + } + return casted +} +func (content *Content) AsTombstone() *TombstoneEventContent { + casted, ok := content.Parsed.(*TombstoneEventContent) + if !ok { + return &TombstoneEventContent{} + } + return casted +} +func (content *Content) AsCreate() *CreateEventContent { + casted, ok := content.Parsed.(*CreateEventContent) + if !ok { + return &CreateEventContent{} + } + return casted +} +func (content *Content) AsJoinRules() *JoinRulesEventContent { + casted, ok := content.Parsed.(*JoinRulesEventContent) + if !ok { + return &JoinRulesEventContent{} + } + return casted +} +func (content *Content) AsHistoryVisibility() *HistoryVisibilityEventContent { + casted, ok := content.Parsed.(*HistoryVisibilityEventContent) + if !ok { + return &HistoryVisibilityEventContent{} + } + return casted +} +func (content *Content) AsGuestAccess() *GuestAccessEventContent { + casted, ok := content.Parsed.(*GuestAccessEventContent) + if !ok { + return &GuestAccessEventContent{} + } + return casted +} +func (content *Content) AsPinnedEvents() *PinnedEventsEventContent { + casted, ok := content.Parsed.(*PinnedEventsEventContent) + if !ok { + return &PinnedEventsEventContent{} + } + return casted +} +func (content *Content) AsEncryption() *EncryptionEventContent { + casted, ok := content.Parsed.(*EncryptionEventContent) + if !ok { + return &EncryptionEventContent{} + } + return casted +} +func (content *Content) AsMessage() *MessageEventContent { + casted, ok := content.Parsed.(*MessageEventContent) + if !ok { + return &MessageEventContent{} + } + return casted +} +func (content *Content) AsEncrypted() *EncryptedEventContent { + casted, ok := content.Parsed.(*EncryptedEventContent) + if !ok { + return &EncryptedEventContent{} + } + return casted +} +func (content *Content) AsRedaction() *RedactionEventContent { + casted, ok := content.Parsed.(*RedactionEventContent) + if !ok { + return &RedactionEventContent{} + } + return casted +} +func (content *Content) AsReaction() *ReactionEventContent { + casted, ok := content.Parsed.(*ReactionEventContent) + if !ok { + return &ReactionEventContent{} + } + return casted +} +func (content *Content) AsTag() *TagEventContent { + casted, ok := content.Parsed.(*TagEventContent) + if !ok { + return &TagEventContent{} + } + return casted +} +func (content *Content) AsDirectChats() *DirectChatsEventContent { + casted, ok := content.Parsed.(*DirectChatsEventContent) + if !ok { + return &DirectChatsEventContent{} + } + return casted +} +func (content *Content) AsFullyRead() *FullyReadEventContent { + casted, ok := content.Parsed.(*FullyReadEventContent) + if !ok { + return &FullyReadEventContent{} + } + return casted +} +func (content *Content) AsIgnoredUserList() *IgnoredUserListEventContent { + casted, ok := content.Parsed.(*IgnoredUserListEventContent) + if !ok { + return &IgnoredUserListEventContent{} + } + return casted +} +func (content *Content) AsTyping() *TypingEventContent { + casted, ok := content.Parsed.(*TypingEventContent) + if !ok { + return &TypingEventContent{} + } + return casted +} +func (content *Content) AsReceipt() *ReceiptEventContent { + casted, ok := content.Parsed.(*ReceiptEventContent) + if !ok { + return &ReceiptEventContent{} + } + return casted +} +func (content *Content) AsPresence() *PresenceEventContent { + casted, ok := content.Parsed.(*PresenceEventContent) + if !ok { + return &PresenceEventContent{} + } + return casted +} +func (content *Content) AsRoomKey() *RoomKeyEventContent { + casted, ok := content.Parsed.(*RoomKeyEventContent) + if !ok { + return &RoomKeyEventContent{} + } + return casted +} +func (content *Content) AsForwardedRoomKey() *ForwardedRoomKeyEventContent { + casted, ok := content.Parsed.(*ForwardedRoomKeyEventContent) + if !ok { + return &ForwardedRoomKeyEventContent{} + } + return casted +} +func (content *Content) AsRoomKeyRequest() *RoomKeyRequestEventContent { + casted, ok := content.Parsed.(*RoomKeyRequestEventContent) + if !ok { + return &RoomKeyRequestEventContent{} + } + return casted +} +func (content *Content) AsRoomKeyWithheld() *RoomKeyWithheldEventContent { + casted, ok := content.Parsed.(*RoomKeyWithheldEventContent) + if !ok { + return &RoomKeyWithheldEventContent{} + } + return casted +} +func (content *Content) AsCallInvite() *CallInviteEventContent { + casted, ok := content.Parsed.(*CallInviteEventContent) + if !ok { + return &CallInviteEventContent{} + } + return casted +} +func (content *Content) AsCallCandidates() *CallCandidatesEventContent { + casted, ok := content.Parsed.(*CallCandidatesEventContent) + if !ok { + return &CallCandidatesEventContent{} + } + return casted +} +func (content *Content) AsCallAnswer() *CallAnswerEventContent { + casted, ok := content.Parsed.(*CallAnswerEventContent) + if !ok { + return &CallAnswerEventContent{} + } + return casted +} +func (content *Content) AsCallReject() *CallRejectEventContent { + casted, ok := content.Parsed.(*CallRejectEventContent) + if !ok { + return &CallRejectEventContent{} + } + return casted +} +func (content *Content) AsCallSelectAnswer() *CallSelectAnswerEventContent { + casted, ok := content.Parsed.(*CallSelectAnswerEventContent) + if !ok { + return &CallSelectAnswerEventContent{} + } + return casted +} +func (content *Content) AsCallNegotiate() *CallNegotiateEventContent { + casted, ok := content.Parsed.(*CallNegotiateEventContent) + if !ok { + return &CallNegotiateEventContent{} + } + return casted +} +func (content *Content) AsCallHangup() *CallHangupEventContent { + casted, ok := content.Parsed.(*CallHangupEventContent) + if !ok { + return &CallHangupEventContent{} + } + return casted +} diff --git a/vendor/maunium.net/go/mautrix/event/encryption.go b/vendor/maunium.net/go/mautrix/event/encryption.go new file mode 100644 index 00000000..4c9bdac3 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/encryption.go @@ -0,0 +1,141 @@ +// 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" + + "maunium.net/go/mautrix/id" +) + +// EncryptionEventContent represents the content of a m.room.encryption state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-encryption +type EncryptionEventContent struct { + // The encryption algorithm to be used to encrypt messages sent in this room. Must be 'm.megolm.v1.aes-sha2'. + Algorithm id.Algorithm `json:"algorithm"` + // How long the session should be used before changing it. 604800000 (a week) is the recommended default. + RotationPeriodMillis int64 `json:"rotation_period_ms,omitempty"` + // How many messages should be sent before changing the session. 100 is the recommended default. + RotationPeriodMessages int `json:"rotation_period_msgs,omitempty"` +} + +// EncryptedEventContent represents the content of a m.room.encrypted message event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-encrypted +type EncryptedEventContent struct { + Algorithm id.Algorithm `json:"algorithm"` + SenderKey id.SenderKey `json:"sender_key"` + DeviceID id.DeviceID `json:"device_id,omitempty"` + SessionID id.SessionID `json:"session_id,omitempty"` + Ciphertext json.RawMessage `json:"ciphertext"` + + MegolmCiphertext []byte `json:"-"` + OlmCiphertext OlmCiphertexts `json:"-"` + + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +type OlmCiphertexts map[id.Curve25519]struct { + Body string `json:"body"` + Type id.OlmMsgType `json:"type"` +} + +type serializableEncryptedEventContent EncryptedEventContent + +func (content *EncryptedEventContent) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, (*serializableEncryptedEventContent)(content)) + if err != nil { + return err + } + switch content.Algorithm { + case id.AlgorithmOlmV1: + content.OlmCiphertext = make(OlmCiphertexts) + return json.Unmarshal(content.Ciphertext, &content.OlmCiphertext) + case id.AlgorithmMegolmV1: + if len(content.Ciphertext) == 0 || content.Ciphertext[0] != '"' || content.Ciphertext[len(content.Ciphertext)-1] != '"' { + return id.InputNotJSONString + } + content.MegolmCiphertext = content.Ciphertext[1 : len(content.Ciphertext)-1] + } + return nil +} + +func (content *EncryptedEventContent) MarshalJSON() ([]byte, error) { + var err error + switch content.Algorithm { + case id.AlgorithmOlmV1: + content.Ciphertext, err = json.Marshal(content.OlmCiphertext) + case id.AlgorithmMegolmV1: + content.Ciphertext = make([]byte, len(content.MegolmCiphertext)+2) + content.Ciphertext[0] = '"' + content.Ciphertext[len(content.Ciphertext)-1] = '"' + copy(content.Ciphertext[1:len(content.Ciphertext)-1], content.MegolmCiphertext) + } + if err != nil { + return nil, err + } + return json.Marshal((*serializableEncryptedEventContent)(content)) +} + +// RoomKeyEventContent represents the content of a m.room_key to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-key +type RoomKeyEventContent struct { + Algorithm id.Algorithm `json:"algorithm"` + RoomID id.RoomID `json:"room_id"` + SessionID id.SessionID `json:"session_id"` + SessionKey string `json:"session_key"` +} + +// ForwardedRoomKeyEventContent represents the content of a m.forwarded_room_key to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-forwarded-room-key +type ForwardedRoomKeyEventContent struct { + RoomKeyEventContent + SenderKey id.SenderKey `json:"sender_key"` + SenderClaimedKey id.Ed25519 `json:"sender_claimed_ed25519_key"` + ForwardingKeyChain []string `json:"forwarding_curve25519_key_chain"` +} + +type KeyRequestAction string + +const ( + KeyRequestActionRequest = "request" + KeyRequestActionCancel = "request_cancellation" +) + +// RoomKeyRequestEventContent represents the content of a m.room_key_request to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-key-request +type RoomKeyRequestEventContent struct { + Body RequestedKeyInfo `json:"body"` + Action KeyRequestAction `json:"action"` + RequestingDeviceID id.DeviceID `json:"requesting_device_id"` + RequestID string `json:"request_id"` +} + +type RequestedKeyInfo struct { + Algorithm id.Algorithm `json:"algorithm"` + RoomID id.RoomID `json:"room_id"` + SenderKey id.SenderKey `json:"sender_key"` + SessionID id.SessionID `json:"session_id"` +} + +type RoomKeyWithheldCode string + +const ( + RoomKeyWithheldBlacklisted RoomKeyWithheldCode = "m.blacklisted" + RoomKeyWithheldUnverified RoomKeyWithheldCode = "m.unverified" + RoomKeyWithheldUnauthorized RoomKeyWithheldCode = "m.unauthorized" + RoomKeyWithheldUnavailable RoomKeyWithheldCode = "m.unavailable" + RoomKeyWithheldNoOlmSession RoomKeyWithheldCode = "m.no_olm" +) + +type RoomKeyWithheldEventContent struct { + RoomID id.RoomID `json:"room_id,omitempty"` + Algorithm id.Algorithm `json:"algorithm"` + SessionID id.SessionID `json:"session_id,omitempty"` + SenderKey id.SenderKey `json:"sender_key"` + Code RoomKeyWithheldCode `json:"code"` + Reason string `json:"reason,omitempty"` +} diff --git a/vendor/maunium.net/go/mautrix/event/ephemeral.go b/vendor/maunium.net/go/mautrix/event/ephemeral.go new file mode 100644 index 00000000..186cb164 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/ephemeral.go @@ -0,0 +1,80 @@ +// 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" + + "maunium.net/go/mautrix/id" +) + +// TagEventContent represents the content of a m.typing ephemeral event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-typing +type TypingEventContent struct { + UserIDs []id.UserID `json:"user_ids"` +} + +// ReceiptEventContent represents the content of a m.receipt ephemeral event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-receipt +type ReceiptEventContent map[id.EventID]Receipts + +type Receipts struct { + Read map[id.UserID]ReadReceipt `json:"m.read"` +} + +type ReadReceipt struct { + Timestamp int64 `json:"ts"` + + // Extra contains any unknown fields in the read receipt event. + // Most servers don't allow clients to set them, so this will be empty in most cases. + Extra map[string]interface{} `json:"-"` +} + +func (rr *ReadReceipt) UnmarshalJSON(data []byte) error { + // Hacky compatibility hack against crappy clients that send double-encoded read receipts. + // TODO is this actually needed? clients can't currently set custom content in receipts 🤔 + if data[0] == '"' && data[len(data)-1] == '"' { + var strData string + err := json.Unmarshal(data, &strData) + if err != nil { + return err + } + data = []byte(strData) + } + + var parsed map[string]interface{} + err := json.Unmarshal(data, &parsed) + if err != nil { + return err + } + ts, _ := parsed["ts"].(float64) + delete(parsed, "ts") + *rr = ReadReceipt{ + Timestamp: int64(ts), + Extra: parsed, + } + return nil +} + +type Presence string + +const ( + PresenceOnline Presence = "online" + PresenceOffline Presence = "offline" + PresenceUnavailable Presence = "unavailable" +) + +// PresenceEventContent represents the content of a m.presence ephemeral event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-presence +type PresenceEventContent struct { + Presence Presence `json:"presence"` + Displayname string `json:"displayname,omitempty"` + AvatarURL id.ContentURIString `json:"avatar_url,omitempty"` + LastActiveAgo int64 `json:"last_active_ago,omitempty"` + CurrentlyActive bool `json:"currently_active,omitempty"` + StatusMessage string `json:"status_msg,omitempty"` +} diff --git a/vendor/maunium.net/go/mautrix/event/events.go b/vendor/maunium.net/go/mautrix/event/events.go new file mode 100644 index 00000000..7f80aeac --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/events.go @@ -0,0 +1,54 @@ +// 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 ( + "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"` // The user ID of the sender of the event + Type Type `json:"type"` // The event type + Timestamp int64 `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server + ID id.EventID `json:"event_id"` // The unique ID of this event + RoomID id.RoomID `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 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:"-"` +} + +type MautrixInfo struct { + Verified 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"` +} + +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"` +} diff --git a/vendor/maunium.net/go/mautrix/event/member.go b/vendor/maunium.net/go/mautrix/event/member.go new file mode 100644 index 00000000..9e4e5250 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/member.go @@ -0,0 +1,53 @@ +// 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" + + "maunium.net/go/mautrix/id" +) + +// Membership is an enum specifying the membership state of a room member. +type Membership string + +func (ms Membership) IsInviteOrJoin() bool { + return ms == MembershipJoin || ms == MembershipInvite +} + +func (ms Membership) IsLeaveOrBan() bool { + return ms == MembershipLeave || ms == MembershipBan +} + +// 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" +) + +// MemberEventContent represents the content of a m.room.member state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-member +type MemberEventContent struct { + Membership Membership `json:"membership"` + AvatarURL id.ContentURIString `json:"avatar_url,omitempty"` + Displayname string `json:"displayname,omitempty"` + IsDirect bool `json:"is_direct,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"` + } +} diff --git a/vendor/maunium.net/go/mautrix/event/message.go b/vendor/maunium.net/go/mautrix/event/message.go new file mode 100644 index 00000000..25d1b6a2 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/message.go @@ -0,0 +1,231 @@ +// 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" + "strconv" + + "maunium.net/go/mautrix/crypto/attachment" + "maunium.net/go/mautrix/id" +) + +// MessageType is the sub-type of a m.room.message event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-message-msgtypes +type MessageType string + +// Msgtypes +const ( + MsgText MessageType = "m.text" + MsgEmote MessageType = "m.emote" + MsgNotice MessageType = "m.notice" + MsgImage MessageType = "m.image" + MsgLocation MessageType = "m.location" + MsgVideo MessageType = "m.video" + MsgAudio MessageType = "m.audio" + MsgFile MessageType = "m.file" + + MsgVerificationRequest MessageType = "m.key.verification.request" +) + +// Format specifies the format of the formatted_body in m.room.message events. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-message-msgtypes +type Format string + +// Message formats +const ( + FormatHTML Format = "org.matrix.custom.html" +) + +// RedactionEventContent represents the content of a m.room.redaction message event. +// +// The redacted event ID is still at the top level, but will move in a future room version. +// See https://github.com/matrix-org/matrix-doc/pull/2244 and https://github.com/matrix-org/matrix-doc/pull/2174 +// +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-redaction +type RedactionEventContent struct { + Reason string `json:"reason,omitempty"` +} + +// ReactionEventContent represents the content of a m.reaction message event. +// This is not yet in a spec release, see https://github.com/matrix-org/matrix-doc/pull/1849 +type ReactionEventContent struct { + RelatesTo RelatesTo `json:"m.relates_to"` +} + +func (content *ReactionEventContent) GetRelatesTo() *RelatesTo { + return &content.RelatesTo +} + +func (content *ReactionEventContent) OptionalGetRelatesTo() *RelatesTo { + return &content.RelatesTo +} + +func (content *ReactionEventContent) SetRelatesTo(rel *RelatesTo) { + content.RelatesTo = *rel +} + +// MssageEventContent represents the content of a m.room.message event. +// +// It is also used to represent m.sticker events, as they are equivalent to m.room.message +// with the exception of the msgtype field. +// +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-message +type MessageEventContent struct { + // Base m.room.message fields + MsgType MessageType `json:"msgtype"` + Body string `json:"body"` + + // Extra fields for text types + Format Format `json:"format,omitempty"` + FormattedBody string `json:"formatted_body,omitempty"` + + // Extra field for m.location + GeoURI string `json:"geo_uri,omitempty"` + + // Extra fields for media types + URL id.ContentURIString `json:"url,omitempty"` + Info *FileInfo `json:"info,omitempty"` + File *EncryptedFileInfo `json:"file,omitempty"` + + // Edits and relations + NewContent *MessageEventContent `json:"m.new_content,omitempty"` + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` + + // In-room verification + To id.UserID `json:"to,omitempty"` + FromDevice id.DeviceID `json:"from_device,omitempty"` + Methods []VerificationMethod `json:"methods,omitempty"` + + replyFallbackRemoved bool +} + +func (content *MessageEventContent) GetRelatesTo() *RelatesTo { + if content.RelatesTo == nil { + content.RelatesTo = &RelatesTo{} + } + return content.RelatesTo +} + +func (content *MessageEventContent) OptionalGetRelatesTo() *RelatesTo { + return content.RelatesTo +} + +func (content *MessageEventContent) SetRelatesTo(rel *RelatesTo) { + content.RelatesTo = rel +} + +func (content *MessageEventContent) GetFile() *EncryptedFileInfo { + if content.File == nil { + content.File = &EncryptedFileInfo{} + } + return content.File +} + +func (content *MessageEventContent) GetInfo() *FileInfo { + if content.Info == nil { + content.Info = &FileInfo{} + } + return content.Info +} + +type EncryptedFileInfo struct { + attachment.EncryptedFile + URL id.ContentURIString `json:"url"` +} + +type FileInfo struct { + MimeType string `json:"mimetype,omitempty"` + ThumbnailInfo *FileInfo `json:"thumbnail_info,omitempty"` + ThumbnailURL id.ContentURIString `json:"thumbnail_url,omitempty"` + ThumbnailFile *EncryptedFileInfo `json:"thumbnail_file,omitempty"` + Width int `json:"-"` + Height int `json:"-"` + Duration int `json:"-"` + Size int `json:"-"` +} + +type serializableFileInfo struct { + MimeType string `json:"mimetype,omitempty"` + ThumbnailInfo *serializableFileInfo `json:"thumbnail_info,omitempty"` + ThumbnailURL id.ContentURIString `json:"thumbnail_url,omitempty"` + ThumbnailFile *EncryptedFileInfo `json:"thumbnail_file,omitempty"` + + Width json.Number `json:"w,omitempty"` + Height json.Number `json:"h,omitempty"` + Duration json.Number `json:"duration,omitempty"` + Size json.Number `json:"size,omitempty"` +} + +func (sfi *serializableFileInfo) CopyFrom(fileInfo *FileInfo) *serializableFileInfo { + if fileInfo == nil { + return nil + } + *sfi = serializableFileInfo{ + MimeType: fileInfo.MimeType, + ThumbnailURL: fileInfo.ThumbnailURL, + ThumbnailInfo: (&serializableFileInfo{}).CopyFrom(fileInfo.ThumbnailInfo), + ThumbnailFile: fileInfo.ThumbnailFile, + } + if fileInfo.Width > 0 { + sfi.Width = json.Number(strconv.Itoa(fileInfo.Width)) + } + if fileInfo.Height > 0 { + sfi.Height = json.Number(strconv.Itoa(fileInfo.Height)) + } + if fileInfo.Size > 0 { + sfi.Size = json.Number(strconv.Itoa(fileInfo.Size)) + + } + if fileInfo.Duration > 0 { + sfi.Duration = json.Number(strconv.Itoa(int(fileInfo.Duration))) + } + return sfi +} + +func (sfi *serializableFileInfo) CopyTo(fileInfo *FileInfo) { + *fileInfo = FileInfo{ + Width: numberToInt(sfi.Width), + Height: numberToInt(sfi.Height), + Size: numberToInt(sfi.Size), + Duration: numberToInt(sfi.Duration), + MimeType: sfi.MimeType, + ThumbnailURL: sfi.ThumbnailURL, + } + if sfi.ThumbnailInfo != nil { + fileInfo.ThumbnailInfo = &FileInfo{} + sfi.ThumbnailInfo.CopyTo(fileInfo.ThumbnailInfo) + } +} + +func (fileInfo *FileInfo) UnmarshalJSON(data []byte) error { + sfi := &serializableFileInfo{} + if err := json.Unmarshal(data, sfi); err != nil { + return err + } + sfi.CopyTo(fileInfo) + return nil +} + +func (fileInfo *FileInfo) MarshalJSON() ([]byte, error) { + return json.Marshal((&serializableFileInfo{}).CopyFrom(fileInfo)) +} + +func numberToInt(val json.Number) int { + f64, _ := val.Float64() + if f64 > 0 { + return int(f64) + } + return 0 +} + +func (fileInfo *FileInfo) GetThumbnailInfo() *FileInfo { + if fileInfo.ThumbnailInfo == nil { + fileInfo.ThumbnailInfo = &FileInfo{} + } + return fileInfo.ThumbnailInfo +} diff --git a/vendor/maunium.net/go/mautrix/event/powerlevels.go b/vendor/maunium.net/go/mautrix/event/powerlevels.go new file mode 100644 index 00000000..3cac9cd7 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/powerlevels.go @@ -0,0 +1,128 @@ +// 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 ( + "sync" + + "maunium.net/go/mautrix/id" +) + +// PowerLevelsEventContent represents the content of a m.room.power_levels state event content. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-power-levels +type PowerLevelsEventContent struct { + usersLock sync.RWMutex `json:"-"` + Users map[id.UserID]int `json:"users"` + UsersDefault int `json:"users_default"` + + eventsLock sync.RWMutex `json:"-"` + Events map[string]int `json:"events"` + EventsDefault int `json:"events_default"` + + 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 *PowerLevelsEventContent) Invite() int { + if pl.InvitePtr != nil { + return *pl.InvitePtr + } + return 50 +} + +func (pl *PowerLevelsEventContent) Kick() int { + if pl.KickPtr != nil { + return *pl.KickPtr + } + return 50 +} + +func (pl *PowerLevelsEventContent) Ban() int { + if pl.BanPtr != nil { + return *pl.BanPtr + } + return 50 +} + +func (pl *PowerLevelsEventContent) Redact() int { + if pl.RedactPtr != nil { + return *pl.RedactPtr + } + return 50 +} + +func (pl *PowerLevelsEventContent) StateDefault() int { + if pl.StateDefaultPtr != nil { + return *pl.StateDefaultPtr + } + return 50 +} + +func (pl *PowerLevelsEventContent) GetUserLevel(userID id.UserID) int { + pl.usersLock.RLock() + defer pl.usersLock.RUnlock() + level, ok := pl.Users[userID] + if !ok { + return pl.UsersDefault + } + return level +} + +func (pl *PowerLevelsEventContent) SetUserLevel(userID id.UserID, level int) { + pl.usersLock.Lock() + defer pl.usersLock.Unlock() + if level == pl.UsersDefault { + delete(pl.Users, userID) + } else { + pl.Users[userID] = level + } +} + +func (pl *PowerLevelsEventContent) EnsureUserLevel(userID id.UserID, level int) bool { + existingLevel := pl.GetUserLevel(userID) + if existingLevel != level { + pl.SetUserLevel(userID, level) + return true + } + return false +} + +func (pl *PowerLevelsEventContent) GetEventLevel(eventType Type) 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 +} + +func (pl *PowerLevelsEventContent) SetEventLevel(eventType Type, 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 *PowerLevelsEventContent) EnsureEventLevel(eventType Type, level int) bool { + existingLevel := pl.GetEventLevel(eventType) + if existingLevel != level { + pl.SetEventLevel(eventType, level) + return true + } + return false +} diff --git a/vendor/maunium.net/go/mautrix/event/relations.go b/vendor/maunium.net/go/mautrix/event/relations.go new file mode 100644 index 00000000..e8b5c231 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/relations.go @@ -0,0 +1,200 @@ +// 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" + + "maunium.net/go/mautrix/id" +) + +type RelationType string + +const ( + RelReplace RelationType = "m.replace" + RelReference RelationType = "m.reference" + RelAnnotation RelationType = "m.annotation" + RelReply RelationType = "net.maunium.reply" +) + +type RelatesTo struct { + Type RelationType + EventID id.EventID + Key string +} + +type serializableInReplyTo struct { + EventID id.EventID `json:"event_id,omitempty"` +} + +type serializableRelatesTo struct { + InReplyTo *serializableInReplyTo `json:"m.in_reply_to,omitempty"` + + Type RelationType `json:"rel_type,omitempty"` + EventID id.EventID `json:"event_id,omitempty"` + Key string `json:"key,omitempty"` +} + +func (rel *RelatesTo) GetReplaceID() id.EventID { + if rel.Type == RelReplace { + return rel.EventID + } + return "" +} + +func (rel *RelatesTo) GetReferenceID() id.EventID { + if rel.Type == RelReference { + return rel.EventID + } + return "" +} + +func (rel *RelatesTo) GetReplyID() id.EventID { + if rel.Type == RelReply { + return rel.EventID + } + return "" +} + +func (rel *RelatesTo) GetAnnotationID() id.EventID { + if rel.Type == RelAnnotation { + return rel.EventID + } + return "" +} + +func (rel *RelatesTo) GetAnnotationKey() string { + if rel.Type == RelAnnotation { + return rel.Key + } + return "" +} + +func (rel *RelatesTo) UnmarshalJSON(data []byte) error { + var srel serializableRelatesTo + if err := json.Unmarshal(data, &srel); err != nil { + return err + } + if len(srel.Type) > 0 { + rel.Type = srel.Type + rel.EventID = srel.EventID + rel.Key = srel.Key + } else if srel.InReplyTo != nil && len(srel.InReplyTo.EventID) > 0 { + rel.Type = RelReply + rel.EventID = srel.InReplyTo.EventID + rel.Key = "" + } + return nil +} + +func (rel *RelatesTo) MarshalJSON() ([]byte, error) { + srel := serializableRelatesTo{Type: rel.Type, EventID: rel.EventID, Key: rel.Key} + if rel.Type == RelReply { + srel.InReplyTo = &serializableInReplyTo{rel.EventID} + } + return json.Marshal(&srel) +} + +type RelationChunkItem struct { + Type RelationType `json:"type"` + EventID string `json:"event_id,omitempty"` + Key string `json:"key,omitempty"` + Count int `json:"count,omitempty"` +} + +type RelationChunk struct { + Chunk []RelationChunkItem `json:"chunk"` + + Limited bool `json:"limited"` + Count int `json:"count"` +} + +type AnnotationChunk struct { + RelationChunk + Map map[string]int `json:"-"` +} + +type serializableAnnotationChunk AnnotationChunk + +func (ac *AnnotationChunk) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, (*serializableAnnotationChunk)(ac)); err != nil { + return err + } + ac.Map = make(map[string]int) + for _, item := range ac.Chunk { + ac.Map[item.Key] += item.Count + } + return nil +} + +func (ac *AnnotationChunk) Serialize() RelationChunk { + ac.Chunk = make([]RelationChunkItem, len(ac.Map)) + i := 0 + for key, count := range ac.Map { + ac.Chunk[i] = RelationChunkItem{ + Type: RelAnnotation, + Key: key, + Count: count, + } + } + return ac.RelationChunk +} + +type EventIDChunk struct { + RelationChunk + List []string `json:"-"` +} + +type serializableEventIDChunk EventIDChunk + +func (ec *EventIDChunk) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, (*serializableEventIDChunk)(ec)); err != nil { + return err + } + for _, item := range ec.Chunk { + ec.List = append(ec.List, item.EventID) + } + return nil +} + +func (ec *EventIDChunk) Serialize(typ RelationType) RelationChunk { + ec.Chunk = make([]RelationChunkItem, len(ec.List)) + for i, eventID := range ec.List { + ec.Chunk[i] = RelationChunkItem{ + Type: typ, + EventID: eventID, + } + } + return ec.RelationChunk +} + +type Relations struct { + Raw map[RelationType]RelationChunk `json:"-"` + + Annotations AnnotationChunk `json:"m.annotation,omitempty"` + References EventIDChunk `json:"m.reference,omitempty"` + Replaces EventIDChunk `json:"m.replace,omitempty"` +} + +type serializableRelations Relations + +func (relations *Relations) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &relations.Raw); err != nil { + return err + } + return json.Unmarshal(data, (*serializableRelations)(relations)) +} + +func (relations *Relations) MarshalJSON() ([]byte, error) { + if relations.Raw == nil { + relations.Raw = make(map[RelationType]RelationChunk) + } + relations.Raw[RelAnnotation] = relations.Annotations.Serialize() + relations.Raw[RelReference] = relations.References.Serialize(RelReference) + relations.Raw[RelReplace] = relations.Replaces.Serialize(RelReplace) + return json.Marshal(relations.Raw) +} diff --git a/vendor/maunium.net/go/mautrix/event/reply.go b/vendor/maunium.net/go/mautrix/event/reply.go new file mode 100644 index 00000000..975d0106 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/reply.go @@ -0,0 +1,108 @@ +// 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 ( + "fmt" + "regexp" + "strings" + + "golang.org/x/net/html" + + "maunium.net/go/mautrix/id" +) + +var HTMLReplyFallbackRegex = regexp.MustCompile(`^[\s\S]+?`) + +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 *MessageEventContent) RemoveReplyFallback() { + if len(content.GetReplyTo()) > 0 && !content.replyFallbackRemoved { + if content.Format == FormatHTML { + content.FormattedBody = TrimReplyFallbackHTML(content.FormattedBody) + } + content.Body = TrimReplyFallbackText(content.Body) + content.replyFallbackRemoved = true + } +} + +func (content *MessageEventContent) GetReplyTo() id.EventID { + if content.RelatesTo != nil && content.RelatesTo.Type == RelReply { + return content.RelatesTo.EventID + } + return "" +} + +const ReplyFormat = `
In reply to %s
%s
` + +func (evt *Event) GenerateReplyFallbackHTML() string { + parsedContent, ok := evt.Content.Parsed.(*MessageEventContent) + if !ok { + return "" + } + parsedContent.RemoveReplyFallback() + body := parsedContent.FormattedBody + if len(body) == 0 { + body = html.EscapeString(parsedContent.Body) + } + + senderDisplayName := evt.Sender + + return fmt.Sprintf(ReplyFormat, evt.RoomID, evt.ID, evt.Sender, senderDisplayName, body) +} + +func (evt *Event) GenerateReplyFallbackText() string { + parsedContent, ok := evt.Content.Parsed.(*MessageEventContent) + if !ok { + return "" + } + parsedContent.RemoveReplyFallback() + body := parsedContent.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 *MessageEventContent) SetReply(inReplyTo *Event) { + content.RelatesTo = &RelatesTo{ + EventID: inReplyTo.ID, + Type: RelReply, + } + + 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 + content.replyFallbackRemoved = false + } +} diff --git a/vendor/maunium.net/go/mautrix/event/state.go b/vendor/maunium.net/go/mautrix/event/state.go new file mode 100644 index 00000000..7184d608 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/state.go @@ -0,0 +1,110 @@ +// 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 ( + "maunium.net/go/mautrix/id" +) + +// CanonicalAliasEventContent represents the content of a m.room.canonical_alias state event. +// https://matrix.org/docs/spec/client_server/r0.6.1#m-room-canonical-alias +type CanonicalAliasEventContent struct { + Alias id.RoomAlias `json:"alias"` + AltAliases []id.RoomAlias `json:"alt_aliases,omitempty"` +} + +// RoomNameEventContent represents the content of a m.room.name state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-name +type RoomNameEventContent struct { + Name string `json:"name"` +} + +// RoomAvatarEventContent represents the content of a m.room.avatar state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-avatar +type RoomAvatarEventContent struct { + URL id.ContentURI `json:"url"` +} + +// TopicEventContent represents the content of a m.room.topic state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-topic +type TopicEventContent struct { + Topic string `json:"topic"` +} + +// TombstoneEventContent represents the content of a m.room.tombstone state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-tombstone +type TombstoneEventContent struct { + Body string `json:"body"` + ReplacementRoom id.RoomID `json:"replacement_room"` +} + +// CreateEventContent represents the content of a m.room.create state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-create +type CreateEventContent struct { + Creator id.UserID `json:"creator"` + Federate bool `json:"m.federate,omitempty"` + RoomVersion string `json:"version,omitempty"` + Predecessor struct { + RoomID id.RoomID `json:"room_id"` + EventID id.EventID `json:"event_id"` + } `json:"predecessor"` +} + +// JoinRule specifies how open a room is to new members. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-join-rules +type JoinRule string + +const ( + JoinRulePublic JoinRule = "public" + JoinRuleKnock JoinRule = "knock" + JoinRuleInvite JoinRule = "invite" + JoinRulePrivate JoinRule = "private" +) + +// JoinRulesEventContent represents the content of a m.room.join_rules state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-join-rules +type JoinRulesEventContent struct { + JoinRule JoinRule `json:"join_rule"` +} + +// PinnedEventsEventContent represents the content of a m.room.pinned_events state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-pinned-events +type PinnedEventsEventContent struct { + Pinned []id.EventID `json:"pinned"` +} + +// HistoryVisibility specifies who can see new messages. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-history-visibility +type HistoryVisibility string + +const ( + HistoryVisibilityInvited HistoryVisibility = "invited" + HistoryVisibilityJoined HistoryVisibility = "joined" + HistoryVisibilityShared HistoryVisibility = "shared" + HistoryVisibilityWorldReadable HistoryVisibility = "world_readable" +) + +// HistoryVisibilityEventContent represents the content of a m.room.history_visibility state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-history-visibility +type HistoryVisibilityEventContent struct { + HistoryVisibility HistoryVisibility `json:"history_visibility"` +} + +// GuestAccess specifies whether or not guest accounts can join. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-guest-access +type GuestAccess string + +const ( + GuestAccessCanJoin GuestAccess = "can_join" + GuestAccessForbidden GuestAccess = "forbidden" +) + +// GuestAccessEventContent represents the content of a m.room.guest_access state event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-room-guest-access +type GuestAccessEventContent struct { + GuestAccess GuestAccess `json:"guest_access"` +} diff --git a/vendor/maunium.net/go/mautrix/event/type.go b/vendor/maunium.net/go/mautrix/event/type.go new file mode 100644 index 00000000..c6cf8545 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/type.go @@ -0,0 +1,235 @@ +// 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" + "fmt" + "strings" +) + +type TypeClass int + +func (tc TypeClass) Name() string { + switch tc { + case MessageEventType: + return "message" + case StateEventType: + return "state" + case EphemeralEventType: + return "ephemeral" + case AccountDataEventType: + return "account data" + case ToDeviceEventType: + return "to-device" + default: + return "unknown" + } +} + +const ( + // Unknown events + UnknownEventType TypeClass = iota + // Normal message events + MessageEventType + // State events + StateEventType + // Ephemeral events + EphemeralEventType + // Account data events + AccountDataEventType + // Device-to-device events + ToDeviceEventType +) + +type Type struct { + Type string + Class TypeClass +} + +func NewEventType(name string) Type { + evtType := Type{Type: name} + evtType.Class = evtType.GuessClass() + return evtType +} + +func (et *Type) IsState() bool { + return et.Class == StateEventType +} + +func (et *Type) IsEphemeral() bool { + return et.Class == EphemeralEventType +} + +func (et *Type) IsAccountData() bool { + return et.Class == AccountDataEventType +} + +func (et *Type) IsToDevice() bool { + return et.Class == ToDeviceEventType +} + +func (et *Type) IsInRoomVerification() bool { + switch et.Type { + case InRoomVerificationStart.Type, InRoomVerificationReady.Type, InRoomVerificationAccept.Type, + InRoomVerificationKey.Type, InRoomVerificationMAC.Type, InRoomVerificationCancel.Type: + return true + default: + return false + } +} + +func (et *Type) IsCall() bool { + switch et.Type { + case CallInvite.Type, CallCandidates.Type, CallAnswer.Type, CallReject.Type, CallSelectAnswer.Type, + CallNegotiate.Type, CallHangup.Type: + return true + default: + return false + } +} + +func (et *Type) IsCustom() bool { + return !strings.HasPrefix(et.Type, "m.") +} + +func (et *Type) GuessClass() TypeClass { + switch et.Type { + case StateAliases.Type, StateCanonicalAlias.Type, StateCreate.Type, StateJoinRules.Type, StateMember.Type, + StatePowerLevels.Type, StateRoomName.Type, StateRoomAvatar.Type, StateTopic.Type, StatePinnedEvents.Type, + StateTombstone.Type, StateEncryption.Type: + return StateEventType + case EphemeralEventReceipt.Type, EphemeralEventTyping.Type, EphemeralEventPresence.Type: + return EphemeralEventType + case AccountDataDirectChats.Type, AccountDataPushRules.Type, AccountDataRoomTags.Type, + AccountDataSecretStorageKey.Type, AccountDataSecretStorageDefaultKey.Type, + AccountDataCrossSigningMaster.Type, AccountDataCrossSigningSelf.Type, AccountDataCrossSigningUser.Type: + return AccountDataEventType + case EventRedaction.Type, EventMessage.Type, EventEncrypted.Type, EventReaction.Type, EventSticker.Type, + InRoomVerificationStart.Type, InRoomVerificationReady.Type, InRoomVerificationAccept.Type, + InRoomVerificationKey.Type, InRoomVerificationMAC.Type, InRoomVerificationCancel.Type, + CallInvite.Type, CallCandidates.Type, CallAnswer.Type, CallReject.Type, CallSelectAnswer.Type, + CallNegotiate.Type, CallHangup.Type: + return MessageEventType + case ToDeviceRoomKey.Type, ToDeviceRoomKeyRequest.Type, ToDeviceForwardedRoomKey.Type, ToDeviceRoomKeyWithheld.Type: + return ToDeviceEventType + default: + return UnknownEventType + } +} + +func (et *Type) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &et.Type) + if err != nil { + return err + } + et.Class = et.GuessClass() + return nil +} + +func (et *Type) MarshalJSON() ([]byte, error) { + return json.Marshal(&et.Type) +} + +func (et Type) UnmarshalText(data []byte) error { + et.Type = string(data) + et.Class = et.GuessClass() + return nil +} + +func (et Type) MarshalText() ([]byte, error) { + return []byte(et.Type), nil +} + +func (et *Type) String() string { + return et.Type +} + +func (et *Type) Repr() string { + return fmt.Sprintf("%s (%s)", et.Type, et.Class.Name()) +} + +// State events +var ( + StateAliases = Type{"m.room.aliases", StateEventType} + StateCanonicalAlias = Type{"m.room.canonical_alias", StateEventType} + StateCreate = Type{"m.room.create", StateEventType} + StateJoinRules = Type{"m.room.join_rules", StateEventType} + StateHistoryVisibility = Type{"m.room.history_visibility", StateEventType} + StateGuestAccess = Type{"m.room.guest_access", StateEventType} + StateMember = Type{"m.room.member", StateEventType} + StatePowerLevels = Type{"m.room.power_levels", StateEventType} + StateRoomName = Type{"m.room.name", StateEventType} + StateTopic = Type{"m.room.topic", StateEventType} + StateRoomAvatar = Type{"m.room.avatar", StateEventType} + StatePinnedEvents = Type{"m.room.pinned_events", StateEventType} + StateTombstone = Type{"m.room.tombstone", StateEventType} + StateEncryption = Type{"m.room.encryption", StateEventType} +) + +// Message events +var ( + EventRedaction = Type{"m.room.redaction", MessageEventType} + EventMessage = Type{"m.room.message", MessageEventType} + EventEncrypted = Type{"m.room.encrypted", MessageEventType} + EventReaction = Type{"m.reaction", MessageEventType} + EventSticker = Type{"m.sticker", MessageEventType} + + InRoomVerificationStart = Type{"m.key.verification.start", MessageEventType} + InRoomVerificationReady = Type{"m.key.verification.ready", MessageEventType} + InRoomVerificationAccept = Type{"m.key.verification.accept", MessageEventType} + InRoomVerificationKey = Type{"m.key.verification.key", MessageEventType} + InRoomVerificationMAC = Type{"m.key.verification.mac", MessageEventType} + InRoomVerificationCancel = Type{"m.key.verification.cancel", MessageEventType} + + CallInvite = Type{"m.call.invite", MessageEventType} + CallCandidates = Type{"m.call.candidates", MessageEventType} + CallAnswer = Type{"m.call.answer", MessageEventType} + CallReject = Type{"m.call.reject", MessageEventType} + CallSelectAnswer = Type{"m.call.select_answer", MessageEventType} + CallNegotiate = Type{"m.call.negotiate", MessageEventType} + CallHangup = Type{"m.call.hangup", MessageEventType} +) + +// Ephemeral events +var ( + EphemeralEventReceipt = Type{"m.receipt", EphemeralEventType} + EphemeralEventTyping = Type{"m.typing", EphemeralEventType} + EphemeralEventPresence = Type{"m.presence", EphemeralEventType} +) + +// Account data events +var ( + AccountDataDirectChats = Type{"m.direct", AccountDataEventType} + AccountDataPushRules = Type{"m.push_rules", AccountDataEventType} + AccountDataRoomTags = Type{"m.tag", AccountDataEventType} + AccountDataFullyRead = Type{"m.fully_read", AccountDataEventType} + AccountDataIgnoredUserList = Type{"m.ignored_user_list", AccountDataEventType} + + AccountDataSecretStorageDefaultKey = Type{"m.secret_storage.default_key", AccountDataEventType} + AccountDataSecretStorageKey = Type{"m.secret_storage.key", AccountDataEventType} + AccountDataCrossSigningMaster = Type{"m.cross_signing.master", AccountDataEventType} + AccountDataCrossSigningUser = Type{"m.cross_signing.user_signing", AccountDataEventType} + AccountDataCrossSigningSelf = Type{"m.cross_signing.self_signing", AccountDataEventType} +) + +// Device-to-device events +var ( + ToDeviceRoomKey = Type{"m.room_key", ToDeviceEventType} + ToDeviceRoomKeyRequest = Type{"m.room_key_request", ToDeviceEventType} + ToDeviceForwardedRoomKey = Type{"m.forwarded_room_key", ToDeviceEventType} + ToDeviceEncrypted = Type{"m.room.encrypted", ToDeviceEventType} + ToDeviceRoomKeyWithheld = Type{"m.room_key.withheld", ToDeviceEventType} + ToDeviceVerificationRequest = Type{"m.key.verification.request", ToDeviceEventType} + ToDeviceVerificationStart = Type{"m.key.verification.start", ToDeviceEventType} + ToDeviceVerificationAccept = Type{"m.key.verification.accept", ToDeviceEventType} + ToDeviceVerificationKey = Type{"m.key.verification.key", ToDeviceEventType} + ToDeviceVerificationMAC = Type{"m.key.verification.mac", ToDeviceEventType} + ToDeviceVerificationCancel = Type{"m.key.verification.cancel", ToDeviceEventType} + + ToDeviceOrgMatrixRoomKeyWithheld = Type{"org.matrix.room_key.withheld", ToDeviceEventType} +) diff --git a/vendor/maunium.net/go/mautrix/event/verification.go b/vendor/maunium.net/go/mautrix/event/verification.go new file mode 100644 index 00000000..800e7b12 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/verification.go @@ -0,0 +1,306 @@ +// Copyright (c) 2020 Nikos Filippakis +// +// 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 ( + "maunium.net/go/mautrix/id" +) + +type VerificationMethod string + +const VerificationMethodSAS VerificationMethod = "m.sas.v1" + +// VerificationRequestEventContent represents the content of a m.key.verification.request to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-key-verification-request +type VerificationRequestEventContent struct { + // The device ID which is initiating the request. + FromDevice id.DeviceID `json:"from_device"` + // An opaque identifier for the verification request. Must be unique with respect to the devices involved. + TransactionID string `json:"transaction_id,omitempty"` + // The verification methods supported by the sender. + Methods []VerificationMethod `json:"methods"` + // The POSIX timestamp in milliseconds for when the request was made. + Timestamp int64 `json:"timestamp,omitempty"` + // The user that the event is sent to for in-room verification. + To id.UserID `json:"to,omitempty"` + // Original event ID for in-room verification. + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +func (vrec *VerificationRequestEventContent) SupportsVerificationMethod(meth VerificationMethod) bool { + for _, supportedMeth := range vrec.Methods { + if supportedMeth == meth { + return true + } + } + return false +} + +type KeyAgreementProtocol string + +const ( + KeyAgreementCurve25519 KeyAgreementProtocol = "curve25519" + KeyAgreementCurve25519HKDFSHA256 KeyAgreementProtocol = "curve25519-hkdf-sha256" +) + +type VerificationHashMethod string + +const VerificationHashSHA256 VerificationHashMethod = "sha256" + +type MACMethod string + +const HKDFHMACSHA256 MACMethod = "hkdf-hmac-sha256" + +type SASMethod string + +const ( + SASDecimal SASMethod = "decimal" + SASEmoji SASMethod = "emoji" +) + +// VerificationStartEventContent represents the content of a m.key.verification.start to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-key-verification-start +type VerificationStartEventContent struct { + // The device ID which is initiating the process. + FromDevice id.DeviceID `json:"from_device"` + // An opaque identifier for the verification process. Must be unique with respect to the devices involved. + TransactionID string `json:"transaction_id,omitempty"` + // The verification method to use. + Method VerificationMethod `json:"method"` + // The key agreement protocols the sending device understands. + KeyAgreementProtocols []KeyAgreementProtocol `json:"key_agreement_protocols"` + // The hash methods the sending device understands. + Hashes []VerificationHashMethod `json:"hashes"` + // The message authentication codes that the sending device understands. + MessageAuthenticationCodes []MACMethod `json:"message_authentication_codes"` + // The SAS methods the sending device (and the sending device's user) understands. + ShortAuthenticationString []SASMethod `json:"short_authentication_string"` + // The user that the event is sent to for in-room verification. + To id.UserID `json:"to,omitempty"` + // Original event ID for in-room verification. + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +func (vsec *VerificationStartEventContent) SupportsKeyAgreementProtocol(proto KeyAgreementProtocol) bool { + for _, supportedProto := range vsec.KeyAgreementProtocols { + if supportedProto == proto { + return true + } + } + return false +} + +func (vsec *VerificationStartEventContent) SupportsHashMethod(alg VerificationHashMethod) bool { + for _, supportedAlg := range vsec.Hashes { + if supportedAlg == alg { + return true + } + } + return false +} + +func (vsec *VerificationStartEventContent) SupportsMACMethod(meth MACMethod) bool { + for _, supportedMeth := range vsec.MessageAuthenticationCodes { + if supportedMeth == meth { + return true + } + } + return false +} + +func (vsec *VerificationStartEventContent) SupportsSASMethod(meth SASMethod) bool { + for _, supportedMeth := range vsec.ShortAuthenticationString { + if supportedMeth == meth { + return true + } + } + return false +} + +func (vsec *VerificationStartEventContent) GetRelatesTo() *RelatesTo { + if vsec.RelatesTo == nil { + vsec.RelatesTo = &RelatesTo{} + } + return vsec.RelatesTo +} + +func (vsec *VerificationStartEventContent) OptionalGetRelatesTo() *RelatesTo { + return vsec.RelatesTo +} + +func (vsec *VerificationStartEventContent) SetRelatesTo(rel *RelatesTo) { + vsec.RelatesTo = rel +} + +// VerificationReadyEventContent represents the content of a m.key.verification.ready event. +type VerificationReadyEventContent struct { + // The device ID which accepted the process. + FromDevice id.DeviceID `json:"from_device"` + // The verification methods supported by the sender. + Methods []VerificationMethod `json:"methods"` + // Original event ID for in-room verification. + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +var _ Relatable = (*VerificationReadyEventContent)(nil) + +func (vrec *VerificationReadyEventContent) GetRelatesTo() *RelatesTo { + if vrec.RelatesTo == nil { + vrec.RelatesTo = &RelatesTo{} + } + return vrec.RelatesTo +} + +func (vrec *VerificationReadyEventContent) OptionalGetRelatesTo() *RelatesTo { + return vrec.RelatesTo +} + +func (vrec *VerificationReadyEventContent) SetRelatesTo(rel *RelatesTo) { + vrec.RelatesTo = rel +} + +// VerificationAcceptEventContent represents the content of a m.key.verification.accept to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-key-verification-accept +type VerificationAcceptEventContent struct { + // An opaque identifier for the verification process. Must be the same as the one used for the m.key.verification.start message. + TransactionID string `json:"transaction_id,omitempty"` + // The verification method to use. + Method VerificationMethod `json:"method"` + // The key agreement protocol the device is choosing to use, out of the options in the m.key.verification.start message. + KeyAgreementProtocol KeyAgreementProtocol `json:"key_agreement_protocol"` + // The hash method the device is choosing to use, out of the options in the m.key.verification.start message. + Hash VerificationHashMethod `json:"hash"` + // The message authentication code the device is choosing to use, out of the options in the m.key.verification.start message. + MessageAuthenticationCode MACMethod `json:"message_authentication_code"` + // The SAS methods both devices involved in the verification process understand. Must be a subset of the options in the m.key.verification.start message. + ShortAuthenticationString []SASMethod `json:"short_authentication_string"` + // The hash (encoded as unpadded base64) of the concatenation of the device's ephemeral public key (encoded as unpadded base64) and the canonical JSON representation of the m.key.verification.start message. + Commitment string `json:"commitment"` + // The user that the event is sent to for in-room verification. + To id.UserID `json:"to,omitempty"` + // Original event ID for in-room verification. + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +func (vaec *VerificationAcceptEventContent) GetRelatesTo() *RelatesTo { + if vaec.RelatesTo == nil { + vaec.RelatesTo = &RelatesTo{} + } + return vaec.RelatesTo +} + +func (vaec *VerificationAcceptEventContent) OptionalGetRelatesTo() *RelatesTo { + return vaec.RelatesTo +} + +func (vaec *VerificationAcceptEventContent) SetRelatesTo(rel *RelatesTo) { + vaec.RelatesTo = rel +} + +// VerificationKeyEventContent represents the content of a m.key.verification.key to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-key-verification-key +type VerificationKeyEventContent struct { + // An opaque identifier for the verification process. Must be the same as the one used for the m.key.verification.start message. + TransactionID string `json:"transaction_id,omitempty"` + // The device's ephemeral public key, encoded as unpadded base64. + Key string `json:"key"` + // The user that the event is sent to for in-room verification. + To id.UserID `json:"to,omitempty"` + // Original event ID for in-room verification. + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +func (vkec *VerificationKeyEventContent) GetRelatesTo() *RelatesTo { + if vkec.RelatesTo == nil { + vkec.RelatesTo = &RelatesTo{} + } + return vkec.RelatesTo +} + +func (vkec *VerificationKeyEventContent) OptionalGetRelatesTo() *RelatesTo { + return vkec.RelatesTo +} + +func (vkec *VerificationKeyEventContent) SetRelatesTo(rel *RelatesTo) { + vkec.RelatesTo = rel +} + +// VerificationMacEventContent represents the content of a m.key.verification.mac to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-key-verification-mac +type VerificationMacEventContent struct { + // An opaque identifier for the verification process. Must be the same as the one used for the m.key.verification.start message. + TransactionID string `json:"transaction_id,omitempty"` + // A map of the key ID to the MAC of the key, using the algorithm in the verification process. The MAC is encoded as unpadded base64. + Mac map[id.KeyID]string `json:"mac"` + // The MAC of the comma-separated, sorted, list of key IDs given in the mac property, encoded as unpadded base64. + Keys string `json:"keys"` + // The user that the event is sent to for in-room verification. + To id.UserID `json:"to,omitempty"` + // Original event ID for in-room verification. + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +func (vmec *VerificationMacEventContent) GetRelatesTo() *RelatesTo { + if vmec.RelatesTo == nil { + vmec.RelatesTo = &RelatesTo{} + } + return vmec.RelatesTo +} + +func (vmec *VerificationMacEventContent) OptionalGetRelatesTo() *RelatesTo { + return vmec.RelatesTo +} + +func (vmec *VerificationMacEventContent) SetRelatesTo(rel *RelatesTo) { + vmec.RelatesTo = rel +} + +type VerificationCancelCode string + +const ( + VerificationCancelByUser VerificationCancelCode = "m.user" + VerificationCancelByTimeout VerificationCancelCode = "m.timeout" + VerificationCancelUnknownTransaction VerificationCancelCode = "m.unknown_transaction" + VerificationCancelUnknownMethod VerificationCancelCode = "m.unknown_method" + VerificationCancelUnexpectedMessage VerificationCancelCode = "m.unexpected_message" + VerificationCancelKeyMismatch VerificationCancelCode = "m.key_mismatch" + VerificationCancelUserMismatch VerificationCancelCode = "m.user_mismatch" + VerificationCancelInvalidMessage VerificationCancelCode = "m.invalid_message" + VerificationCancelAccepted VerificationCancelCode = "m.accepted" + VerificationCancelSASMismatch VerificationCancelCode = "m.mismatched_sas" + VerificationCancelCommitmentMismatch VerificationCancelCode = "m.mismatched_commitment" +) + +// VerificationCancelEventContent represents the content of a m.key.verification.cancel to_device event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-key-verification-cancel +type VerificationCancelEventContent struct { + // The opaque identifier for the verification process/request. + TransactionID string `json:"transaction_id,omitempty"` + // A human readable description of the code. The client should only rely on this string if it does not understand the code. + Reason string `json:"reason"` + // The error code for why the process/request was cancelled by the user. + Code VerificationCancelCode `json:"code"` + // The user that the event is sent to for in-room verification. + To id.UserID `json:"to,omitempty"` + // Original event ID for in-room verification. + RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` +} + +func (vcec *VerificationCancelEventContent) GetRelatesTo() *RelatesTo { + if vcec.RelatesTo == nil { + vcec.RelatesTo = &RelatesTo{} + } + return vcec.RelatesTo +} + +func (vcec *VerificationCancelEventContent) OptionalGetRelatesTo() *RelatesTo { + return vcec.RelatesTo +} + +func (vcec *VerificationCancelEventContent) SetRelatesTo(rel *RelatesTo) { + vcec.RelatesTo = rel +} diff --git a/vendor/maunium.net/go/mautrix/event/voip.go b/vendor/maunium.net/go/mautrix/event/voip.go new file mode 100644 index 00000000..28f56c95 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/event/voip.go @@ -0,0 +1,116 @@ +// Copyright (c) 2021 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" + "fmt" + "strconv" +) + +type CallHangupReason string + +const ( + CallHangupICEFailed CallHangupReason = "ice_failed" + CallHangupInviteTimeout CallHangupReason = "invite_timeout" + CallHangupUserHangup CallHangupReason = "user_hangup" + CallHangupUserMediaFailed CallHangupReason = "user_media_failed" + CallHangupUnknownError CallHangupReason = "unknown_error" +) + +type CallDataType string + +const ( + CallDataTypeOffer CallDataType = "offer" + CallDataTypeAnswer CallDataType = "answer" +) + +type CallData struct { + SDP string `json:"sdp"` + Type CallDataType `json:"type"` +} + +type CallCandidate struct { + Candidate string `json:"candidate"` + SDPMLineIndex int `json:"sdpMLineIndex"` + SDPMID string `json:"sdpMid"` +} + +type CallVersion string + +func (cv *CallVersion) UnmarshalJSON(raw []byte) error { + var numberVersion int + err := json.Unmarshal(raw, &numberVersion) + if err != nil { + var stringVersion string + err = json.Unmarshal(raw, &stringVersion) + if err != nil { + return fmt.Errorf("failed to parse CallVersion: %w", err) + } + *cv = CallVersion(stringVersion) + } else { + *cv = CallVersion(strconv.Itoa(numberVersion)) + } + return nil +} + +func (cv *CallVersion) MarshalJSON() ([]byte, error) { + for _, char := range *cv { + if char < '0' || char > '9' { + // The version contains weird characters, return as string. + return json.Marshal(string(*cv)) + } + } + // The version consists of only ASCII digits, return as an integer. + return []byte(*cv), nil +} + +func (cv *CallVersion) Int() (int, error) { + return strconv.Atoi(string(*cv)) +} + +type BaseCallEventContent struct { + CallID string `json:"call_id"` + PartyID string `json:"party_id"` + Version CallVersion `json:"version"` +} + +type CallInviteEventContent struct { + BaseCallEventContent + Lifetime int `json:"lifetime"` + Offer CallData `json:"offer"` +} + +type CallCandidatesEventContent struct { + BaseCallEventContent + Candidates []CallCandidate `json:"candidates"` +} + +type CallRejectEventContent struct { + BaseCallEventContent +} + +type CallAnswerEventContent struct { + BaseCallEventContent + Answer CallData `json:"answer"` +} + +type CallSelectAnswerEventContent struct { + BaseCallEventContent + SelectedPartyID string `json:"selected_party_id"` +} + +type CallNegotiateEventContent struct { + BaseCallEventContent + Lifetime int `json:"lifetime"` + Description CallData `json:"description"` +} + +type CallHangupEventContent struct { + BaseCallEventContent + Reason CallHangupReason `json:"reason"` +} diff --git a/vendor/maunium.net/go/mautrix/filter.go b/vendor/maunium.net/go/mautrix/filter.go new file mode 100644 index 00000000..71235f2d --- /dev/null +++ b/vendor/maunium.net/go/mautrix/filter.go @@ -0,0 +1,93 @@ +// Copyright 2017 Jan Christian Grünhage + +package mautrix + +import ( + "errors" + + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +type EventFormat string + +const ( + EventFormatClient EventFormat = "client" + EventFormatFederation EventFormat = "federation" +) + +//Filter is used by clients to specify how the server should filter responses to e.g. sync requests +//Specified by: https://matrix.org/docs/spec/client_server/r0.6.0.html#filtering +type Filter struct { + AccountData FilterPart `json:"account_data,omitempty"` + EventFields []string `json:"event_fields,omitempty"` + EventFormat EventFormat `json:"event_format,omitempty"` + Presence FilterPart `json:"presence,omitempty"` + Room RoomFilter `json:"room,omitempty"` +} + +// RoomFilter is used to define filtering rules for room events +type RoomFilter struct { + AccountData FilterPart `json:"account_data,omitempty"` + Ephemeral FilterPart `json:"ephemeral,omitempty"` + IncludeLeave bool `json:"include_leave,omitempty"` + NotRooms []id.RoomID `json:"not_rooms,omitempty"` + Rooms []id.RoomID `json:"rooms,omitempty"` + State FilterPart `json:"state,omitempty"` + Timeline FilterPart `json:"timeline,omitempty"` +} + +// FilterPart is used to define filtering rules for specific categories of events +type FilterPart struct { + NotRooms []id.RoomID `json:"not_rooms,omitempty"` + Rooms []id.RoomID `json:"rooms,omitempty"` + Limit int `json:"limit,omitempty"` + NotSenders []id.UserID `json:"not_senders,omitempty"` + NotTypes []event.Type `json:"not_types,omitempty"` + Senders []id.UserID `json:"senders,omitempty"` + Types []event.Type `json:"types,omitempty"` + ContainsURL *bool `json:"contains_url,omitempty"` + + LazyLoadMembers bool `json:"lazy_load_members,omitempty"` + IncludeRedundantMembers bool `json:"include_redundant_members,omitempty"` +} + +// Validate checks if the filter contains valid property values +func (filter *Filter) Validate() error { + if filter.EventFormat != EventFormatClient && filter.EventFormat != EventFormatFederation { + return errors.New("Bad event_format value. Must be one of [\"client\", \"federation\"]") + } + return nil +} + +// DefaultFilter returns the default filter used by the Matrix server if no filter is provided in the request +func DefaultFilter() Filter { + return Filter{ + AccountData: DefaultFilterPart(), + EventFields: nil, + EventFormat: "client", + Presence: DefaultFilterPart(), + Room: RoomFilter{ + AccountData: DefaultFilterPart(), + Ephemeral: DefaultFilterPart(), + IncludeLeave: false, + NotRooms: nil, + Rooms: nil, + State: DefaultFilterPart(), + Timeline: DefaultFilterPart(), + }, + } +} + +// DefaultFilterPart returns the default filter part used by the Matrix server if no filter is provided in the request +func DefaultFilterPart() FilterPart { + return FilterPart{ + NotRooms: nil, + Rooms: nil, + Limit: 20, + NotSenders: nil, + NotTypes: nil, + Senders: nil, + Types: nil, + } +} diff --git a/vendor/maunium.net/go/mautrix/go.mod b/vendor/maunium.net/go/mautrix/go.mod new file mode 100644 index 00000000..999cde5f --- /dev/null +++ b/vendor/maunium.net/go/mautrix/go.mod @@ -0,0 +1,19 @@ +module maunium.net/go/mautrix + +go 1.14 + +require ( + github.com/btcsuite/btcutil v1.0.2 + github.com/gorilla/mux v1.8.0 + github.com/gorilla/websocket v1.4.2 + github.com/lib/pq v1.9.0 + github.com/mattn/go-sqlite3 v1.14.6 + github.com/russross/blackfriday/v2 v2.1.0 + github.com/stretchr/testify v1.6.1 + github.com/tidwall/gjson v1.6.8 + github.com/tidwall/sjson v1.1.5 + golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 + golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d + gopkg.in/yaml.v2 v2.3.0 + maunium.net/go/maulogger/v2 v2.2.4 +) diff --git a/vendor/maunium.net/go/mautrix/go.sum b/vendor/maunium.net/go/mautrix/go.sum new file mode 100644 index 00000000..ba7aa381 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/go.sum @@ -0,0 +1,77 @@ +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w= +github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE= +github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +maunium.net/go/maulogger/v2 v2.2.4 h1:oV2GDeM4fx1uRysdpDC0FcrPg+thFicSd9XzPcYMbVY= +maunium.net/go/maulogger/v2 v2.2.4/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A= diff --git a/vendor/maunium.net/go/mautrix/id/contenturi.go b/vendor/maunium.net/go/mautrix/id/contenturi.go new file mode 100644 index 00000000..967bba9a --- /dev/null +++ b/vendor/maunium.net/go/mautrix/id/contenturi.go @@ -0,0 +1,133 @@ +// 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 id + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "strings" +) + +var ( + InvalidContentURI = errors.New("invalid Matrix content URI") + InputNotJSONString = errors.New("input doesn't look like a JSON string") +) + +// ContentURIString is a string that's expected to be a Matrix content URI. +// It's useful for delaying the parsing of the content URI to move errors from the event content +// JSON parsing step to a later step where more appropriate errors can be produced. +type ContentURIString string + +func (uriString ContentURIString) Parse() (ContentURI, error) { + return ParseContentURI(string(uriString)) +} + +func (uriString ContentURIString) ParseOrIgnore() ContentURI { + parsed, _ := ParseContentURI(string(uriString)) + return parsed +} + +// ContentURI represents a Matrix content URI. +// https://matrix.org/docs/spec/client_server/r0.6.0#matrix-content-mxc-uris +type ContentURI struct { + Homeserver string + FileID string +} + +func MustParseContentURI(uri string) ContentURI { + parsed, err := ParseContentURI(uri) + if err != nil { + panic(err) + } + return parsed +} + +// ParseContentURI parses a Matrix content URI. +func ParseContentURI(uri string) (parsed ContentURI, err error) { + if len(uri) == 0 { + return + } else if !strings.HasPrefix(uri, "mxc://") { + err = InvalidContentURI + } else if index := strings.IndexRune(uri[6:], '/'); index == -1 || index == len(uri)-7 { + err = InvalidContentURI + } else { + parsed.Homeserver = uri[6 : 6+index] + parsed.FileID = uri[6+index+1:] + } + return +} + +var mxcBytes = []byte("mxc://") + +func ParseContentURIBytes(uri []byte) (parsed ContentURI, err error) { + if len(uri) == 0 { + return + } else if !bytes.HasPrefix(uri, mxcBytes) { + err = InvalidContentURI + } else if index := bytes.IndexRune(uri[6:], '/'); index == -1 || index == len(uri)-7 { + err = InvalidContentURI + } else { + parsed.Homeserver = string(uri[6 : 6+index]) + parsed.FileID = string(uri[6+index+1:]) + } + return +} + +func (uri *ContentURI) UnmarshalJSON(raw []byte) (err error) { + if string(raw) == "null" { + *uri = ContentURI{} + return nil + } else if len(raw) < 2 || raw[0] != '"' || raw[len(raw)-1] != '"' { + return InputNotJSONString + } + parsed, err := ParseContentURIBytes(raw[1:len(raw)-1]) + if err != nil { + return err + } + *uri = parsed + return nil +} + +func (uri *ContentURI) MarshalJSON() ([]byte, error) { + if uri.IsEmpty() { + return []byte("null"), nil + } + return json.Marshal(uri.String()) +} + +func (uri *ContentURI) UnmarshalText(raw []byte) (err error) { + parsed, err := ParseContentURIBytes(raw) + if err != nil { + return err + } + *uri = parsed + return nil +} + +func (uri ContentURI) MarshalText() ([]byte, error) { + if uri.IsEmpty() { + return []byte(""), nil + } + return []byte(uri.String()), nil +} + +func (uri *ContentURI) String() string { + if uri.IsEmpty() { + return "" + } + return fmt.Sprintf("mxc://%s/%s", uri.Homeserver, uri.FileID) +} + +func (uri *ContentURI) CUString() ContentURIString { + return ContentURIString(uri.String()) +} + +func (uri *ContentURI) IsEmpty() bool { + return len(uri.Homeserver) == 0 || len(uri.FileID) == 0 +} diff --git a/vendor/maunium.net/go/mautrix/id/crypto.go b/vendor/maunium.net/go/mautrix/id/crypto.go new file mode 100644 index 00000000..5683cc47 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/id/crypto.go @@ -0,0 +1,114 @@ +// 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 id + +import ( + "fmt" + "strings" +) + +// OlmMsgType is an Olm message type +type OlmMsgType int + +const ( + OlmMsgTypePreKey OlmMsgType = 0 + OlmMsgTypeMsg OlmMsgType = 1 +) + +// Algorithm is a Matrix message encryption algorithm. +// https://matrix.org/docs/spec/client_server/r0.6.0#messaging-algorithm-names +type Algorithm string + +const ( + AlgorithmOlmV1 Algorithm = "m.olm.v1.curve25519-aes-sha2" + AlgorithmMegolmV1 Algorithm = "m.megolm.v1.aes-sha2" +) + +type KeyAlgorithm string + +const ( + KeyAlgorithmCurve25519 KeyAlgorithm = "curve25519" + KeyAlgorithmEd25519 KeyAlgorithm = "ed25519" + KeyAlgorithmSignedCurve25519 KeyAlgorithm = "signed_curve25519" +) + +type CrossSigningUsage string + +const ( + XSUsageMaster CrossSigningUsage = "master" + XSUsageSelfSigning CrossSigningUsage = "self_signing" + XSUsageUserSigning CrossSigningUsage = "user_signing" +) + +// A SessionID is an arbitrary string that identifies an Olm or Megolm session. +type SessionID string + +func (sessionID SessionID) String() string { + return string(sessionID) +} + +// Ed25519 is the base64 representation of an Ed25519 public key +type Ed25519 string +type SigningKey = Ed25519 + +func (ed25519 Ed25519) String() string { + return string(ed25519) +} + +// Curve25519 is the base64 representation of an Curve25519 public key +type Curve25519 string +type SenderKey = Curve25519 +type IdentityKey = Curve25519 + +func (curve25519 Curve25519) String() string { + return string(curve25519) +} + +// A DeviceID is an arbitrary string that references a specific device. +type DeviceID string + +func (deviceID DeviceID) String() string { + return string(deviceID) +} + +// A DeviceKeyID is a string formatted as : that is used as the key in deviceid-key mappings. +type DeviceKeyID string + +func NewDeviceKeyID(algorithm KeyAlgorithm, deviceID DeviceID) DeviceKeyID { + return DeviceKeyID(fmt.Sprintf("%s:%s", algorithm, deviceID)) +} + +func (deviceKeyID DeviceKeyID) String() string { + return string(deviceKeyID) +} + +func (deviceKeyID DeviceKeyID) Parse() (Algorithm, DeviceID) { + index := strings.IndexRune(string(deviceKeyID), ':') + if index < 0 || len(deviceKeyID) <= index+1 { + return "", "" + } + return Algorithm(deviceKeyID[:index]), DeviceID(deviceKeyID[index+1:]) +} + +// A KeyID a string formatted as : that is used as the key in one-time-key mappings. +type KeyID string + +func NewKeyID(algorithm KeyAlgorithm, keyID string) KeyID { + return KeyID(fmt.Sprintf("%s:%s", algorithm, keyID)) +} + +func (keyID KeyID) String() string { + return string(keyID) +} + +func (keyID KeyID) Parse() (KeyAlgorithm, string) { + index := strings.IndexRune(string(keyID), ':') + if index < 0 || len(keyID) <= index+1 { + return "", "" + } + return KeyAlgorithm(keyID[:index]), string(keyID[index+1:]) +} diff --git a/vendor/maunium.net/go/mautrix/id/matrixuri.go b/vendor/maunium.net/go/mautrix/id/matrixuri.go new file mode 100644 index 00000000..1742d457 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/id/matrixuri.go @@ -0,0 +1,293 @@ +// Copyright (c) 2021 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 id + +import ( + "errors" + "fmt" + "net/url" + "strings" +) + +// Errors that can happen when parsing matrix: URIs +var ( + ErrInvalidScheme = errors.New("matrix URI scheme must be exactly 'matrix'") + ErrInvalidPartCount = errors.New("matrix URIs must have exactly 2 or 4 segments") + ErrInvalidFirstSegment = errors.New("invalid identifier in first segment of matrix URI") + ErrEmptySecondSegment = errors.New("the second segment of the matrix URI must not be empty") + ErrInvalidThirdSegment = errors.New("invalid identifier in third segment of matrix URI") + ErrEmptyFourthSegment = errors.New("the fourth segment of the matrix URI must not be empty when the third segment is present") +) + +// Errors that can happen when parsing matrix.to URLs +var ( + ErrNotMatrixTo = errors.New("that URL is not a matrix.to URL") + ErrInvalidMatrixToPartCount = errors.New("matrix.to URLs must have exactly 1 or 2 segments") + ErrEmptyMatrixToPrimaryIdentifier = errors.New("the primary identifier in the matrix.to URL is empty") + ErrInvalidMatrixToPrimaryIdentifier = errors.New("the primary identifier in the matrix.to URL has an invalid sigil") + ErrInvalidMatrixToSecondaryIdentifier = errors.New("the secondary identifier in the matrix.to URL has an invalid sigil") +) + +var ErrNotMatrixToOrMatrixURI = errors.New("that URL is not a matrix.to URL nor matrix: URI") + +// MatrixURI contains the result of parsing a matrix: URI using ParseMatrixURI +type MatrixURI struct { + Sigil1 rune + Sigil2 rune + MXID1 string + MXID2 string + Via []string + Action string +} + +// SigilToPathSegment contains a mapping from Matrix identifier sigils to matrix: URI path segments. +var SigilToPathSegment = map[rune]string{ + '$': "e", + '#': "r", + '!': "roomid", + '@': "u", +} + +func (uri *MatrixURI) getQuery() url.Values { + q := make(url.Values) + if uri.Via != nil && len(uri.Via) > 0 { + q["via"] = uri.Via + } + if len(uri.Action) > 0 { + q.Set("action", uri.Action) + } + return q +} + +// String converts the parsed matrix: URI back into the string representation. +func (uri *MatrixURI) String() string { + parts := []string{ + SigilToPathSegment[uri.Sigil1], + uri.MXID1, + } + if uri.Sigil2 != 0 { + parts = append(parts, SigilToPathSegment[uri.Sigil2], uri.MXID2) + } + return (&url.URL{ + Scheme: "matrix", + Opaque: strings.Join(parts, "/"), + RawQuery: uri.getQuery().Encode(), + }).String() +} + +// MatrixToURL converts to parsed matrix: URI into a matrix.to URL +func (uri *MatrixURI) MatrixToURL() string { + fragment := fmt.Sprintf("#/%s", url.QueryEscape(uri.PrimaryIdentifier())) + if uri.Sigil2 != 0 { + fragment = fmt.Sprintf("%s/%s", fragment, url.QueryEscape(uri.SecondaryIdentifier())) + } + query := uri.getQuery().Encode() + if len(query) > 0 { + fragment = fmt.Sprintf("%s?%s", fragment, query) + } + // It would be nice to use URL{...}.String() here, but figuring out the Fragment vs RawFragment stuff is a pain + return fmt.Sprintf("https://matrix.to/%s", fragment) +} + +// PrimaryIdentifier returns the first Matrix identifier in the URI. +// Currently room IDs, room aliases and user IDs can be in the primary identifier slot. +func (uri *MatrixURI) PrimaryIdentifier() string { + return fmt.Sprintf("%c%s", uri.Sigil1, uri.MXID1) +} + +// SecondaryIdentifier returns the second Matrix identifier in the URI. +// Currently only event IDs can be in the secondary identifier slot. +func (uri *MatrixURI) SecondaryIdentifier() string { + if uri.Sigil2 == 0 { + return "" + } + return fmt.Sprintf("%c%s", uri.Sigil2, uri.MXID2) +} + +// UserID returns the user ID from the URI if the primary identifier is a user ID. +func (uri *MatrixURI) UserID() UserID { + if uri.Sigil1 == '@' { + return UserID(uri.PrimaryIdentifier()) + } + return "" +} + +// RoomID returns the room ID from the URI if the primary identifier is a room ID. +func (uri *MatrixURI) RoomID() RoomID { + if uri.Sigil1 == '!' { + return RoomID(uri.PrimaryIdentifier()) + } + return "" +} + +// RoomAlias returns the room alias from the URI if the primary identifier is a room alias. +func (uri *MatrixURI) RoomAlias() RoomAlias { + if uri.Sigil1 == '#' { + return RoomAlias(uri.PrimaryIdentifier()) + } + return "" +} + +// EventID returns the event ID from the URI if the primary identifier is a room ID or alias and the secondary identifier is an event ID. +func (uri *MatrixURI) EventID() EventID { + if (uri.Sigil1 == '!' || uri.Sigil1 == '#') && uri.Sigil2 == '$' { + return EventID(uri.SecondaryIdentifier()) + } + return "" +} + +// ParseMatrixURIOrMatrixToURL parses the given matrix.to URL or matrix: URI into a unified representation. +func ParseMatrixURIOrMatrixToURL(uri string) (*MatrixURI, error) { + parsed, err := url.Parse(uri) + if err != nil { + return nil, fmt.Errorf("failed to parse URI: %w", err) + } + if parsed.Scheme == "matrix" { + return ProcessMatrixURI(parsed) + } else if strings.HasSuffix(parsed.Hostname(), "matrix.to") { + return ProcessMatrixToURL(parsed) + } else { + return nil, ErrNotMatrixToOrMatrixURI + } +} + +// ParseMatrixURI implements the matrix: URI parsing algorithm. +// +// Currently specified in https://github.com/matrix-org/matrix-doc/blob/master/proposals/2312-matrix-uri.md#uri-parsing-algorithm +func ParseMatrixURI(uri string) (*MatrixURI, error) { + // Step 1: parse the URI according to RFC 3986 + parsed, err := url.Parse(uri) + if err != nil { + return nil, fmt.Errorf("failed to parse URI: %w", err) + } + return ProcessMatrixURI(parsed) +} + +// ProcessMatrixURI implements steps 2-7 of the matrix: URI parsing algorithm +// (i.e. everything except parsing the URI itself, which is done with url.Parse or ParseMatrixURI) +func ProcessMatrixURI(uri *url.URL) (*MatrixURI, error) { + // Step 2: check that scheme is exactly `matrix` + if uri.Scheme != "matrix" { + return nil, ErrInvalidScheme + } + + // Step 3: split the path into segments separated by / + parts := strings.Split(uri.Opaque, "/") + + // Step 4: Check that the URI contains either 2 or 4 segments + if len(parts) != 2 && len(parts) != 4 { + return nil, ErrInvalidPartCount + } + + var parsed MatrixURI + + // Step 5: Construct the top-level Matrix identifier + // a: find the sigil from the first segment + switch parts[0] { + case "u", "user": + parsed.Sigil1 = '@' + case "r", "room": + parsed.Sigil1 = '#' + case "roomid": + parsed.Sigil1 = '!' + default: + return nil, fmt.Errorf("%w: '%s'", ErrInvalidFirstSegment, parts[0]) + } + // b: find the identifier from the second segment + if len(parts[1]) == 0 { + return nil, ErrEmptySecondSegment + } + parsed.MXID1 = parts[1] + + // Step 6: if the first part is a room and the URI has 4 segments, construct a second level identifier + if (parsed.Sigil1 == '!' || parsed.Sigil1 == '#') && len(parts) == 4 { + // a: find the sigil from the third segment + switch parts[2] { + case "e", "event": + parsed.Sigil2 = '$' + default: + return nil, fmt.Errorf("%w: '%s'", ErrInvalidThirdSegment, parts[0]) + } + + // b: find the identifier from the fourth segment + if len(parts[3]) == 0 { + return nil, ErrEmptyFourthSegment + } + parsed.MXID2 = parts[3] + } + + // Step 7: parse the query and extract via and action items + via, ok := uri.Query()["via"] + if ok && len(via) > 0 { + parsed.Via = via + } + action, ok := uri.Query()["action"] + if ok && len(action) > 0 { + parsed.Action = action[len(action)-1] + } + + return &parsed, nil +} + +// ParseMatrixToURL parses a matrix.to URL into the same container as ParseMatrixURI parses matrix: URIs. +func ParseMatrixToURL(uri string) (*MatrixURI, error) { + parsed, err := url.Parse(uri) + if err != nil { + return nil, fmt.Errorf("failed to parse URL: %w", err) + } + return ProcessMatrixToURL(parsed) +} + +// ProcessMatrixToURL is the equivalent of ProcessMatrixURI for matrix.to URLs. +func ProcessMatrixToURL(uri *url.URL) (*MatrixURI, error) { + if !strings.HasSuffix(uri.Hostname(), "matrix.to") { + return nil, ErrNotMatrixTo + } + + initialSplit := strings.SplitN(uri.Fragment, "?", 2) + parts := strings.Split(initialSplit[0], "/") + if len(initialSplit) > 1 { + uri.RawQuery = initialSplit[1] + } + + if len(parts) < 2 || len(parts) > 3 { + return nil, ErrInvalidMatrixToPartCount + } + + if len(parts[1]) == 0 { + return nil, ErrEmptyMatrixToPrimaryIdentifier + } + + var parsed MatrixURI + + parsed.Sigil1 = rune(parts[1][0]) + parsed.MXID1 = parts[1][1:] + _, isKnown := SigilToPathSegment[parsed.Sigil1] + if !isKnown { + return nil, ErrInvalidMatrixToPrimaryIdentifier + } + + if len(parts) == 3 && len(parts[2]) > 0 { + parsed.Sigil2 = rune(parts[2][0]) + parsed.MXID2 = parts[2][1:] + _, isKnown = SigilToPathSegment[parsed.Sigil2] + if !isKnown { + return nil, ErrInvalidMatrixToSecondaryIdentifier + } + } + + via, ok := uri.Query()["via"] + if ok && len(via) > 0 { + parsed.Via = via + } + action, ok := uri.Query()["action"] + if ok && len(action) > 0 { + parsed.Action = action[len(action)-1] + } + + return &parsed, nil +} diff --git a/vendor/maunium.net/go/mautrix/id/opaque.go b/vendor/maunium.net/go/mautrix/id/opaque.go new file mode 100644 index 00000000..0f632f11 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/id/opaque.go @@ -0,0 +1,41 @@ +// 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 id + +import ( + "fmt" +) + +// A RoomID is a string starting with ! that references a specific room. +// https://matrix.org/docs/spec/appendices#room-ids-and-event-ids +type RoomID string + +// A RoomAlias is a string starting with # that can be resolved into. +// https://matrix.org/docs/spec/appendices#room-aliases +type RoomAlias string + +func NewRoomAlias(localpart, server string) RoomAlias { + return RoomAlias(fmt.Sprintf("#%s:%s", localpart, server)) +} + +// An EventID is a string starting with $ that references a specific event. +// +// https://matrix.org/docs/spec/appendices#room-ids-and-event-ids +// https://matrix.org/docs/spec/rooms/v4#event-ids +type EventID string + +func (roomID RoomID) String() string { + return string(roomID) +} + +func (roomAlias RoomAlias) String() string { + return string(roomAlias) +} + +func (eventID EventID) String() string { + return string(eventID) +} diff --git a/vendor/github.com/matrix-org/gomatrix/userids.go b/vendor/maunium.net/go/mautrix/id/userid.go similarity index 57% rename from vendor/github.com/matrix-org/gomatrix/userids.go rename to vendor/maunium.net/go/mautrix/id/userid.go index 70002c5b..67199419 100644 --- a/vendor/github.com/matrix-org/gomatrix/userids.go +++ b/vendor/maunium.net/go/mautrix/id/userid.go @@ -1,12 +1,95 @@ -package gomatrix +// Copyright (c) 2021 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 id import ( "bytes" "encoding/hex" + "errors" "fmt" + "regexp" "strings" ) +// UserID represents a Matrix user ID. +// https://matrix.org/docs/spec/appendices#user-identifiers +type UserID string + +const UserIDMaxLength = 255 + +func NewUserID(localpart, homeserver string) UserID { + return UserID(fmt.Sprintf("@%s:%s", localpart, homeserver)) +} + +func NewEncodedUserID(localpart, homeserver string) UserID { + return NewUserID(EncodeUserLocalpart(localpart), homeserver) +} + +var ( + ErrInvalidUserID = errors.New("is not a valid user ID") + ErrNoncompliantLocalpart = errors.New("contains characters that are not allowed") + ErrUserIDTooLong = errors.New("the given user ID is longer than 255 characters") + ErrEmptyLocalpart = errors.New("empty localparts are not allowed") +) + +// Parse parses the user ID into the localpart and server name. +// +// Note that this only enforces very basic user ID formatting requirements: user IDs start with +// a @, and contain a : after the @. If you want to enforce localpart validity, see the +// ParseAndValidate and ValidateUserLocalpart functions. +func (userID UserID) Parse() (localpart, homeserver string, err error) { + if len(userID) == 0 || userID[0] != '@' || !strings.ContainsRune(string(userID), ':') { + // This error wrapping lets you use errors.Is() nicely even though the message contains the user ID + err = fmt.Errorf("'%s' %w", userID, ErrInvalidUserID) + return + } + parts := strings.SplitN(string(userID), ":", 2) + localpart, homeserver = strings.TrimPrefix(parts[0], "@"), parts[1] + return +} + +var ValidLocalpartRegex = regexp.MustCompile("^[0-9a-z-.=_/]+$") + +// ValidateUserLocalpart validates a Matrix user ID localpart using the grammar +// in https://matrix.org/docs/spec/appendices#user-identifier +func ValidateUserLocalpart(localpart string) error { + if len(localpart) == 0 { + return ErrEmptyLocalpart + } else if !ValidLocalpartRegex.MatchString(localpart) { + return fmt.Errorf("'%s' %w", localpart, ErrNoncompliantLocalpart) + } + return nil +} + +// ParseAndValidate parses the user ID into the localpart and server name like Parse, +// and also validates that the localpart is allowed according to the user identifiers spec. +func (userID UserID) ParseAndValidate() (localpart, homeserver string, err error) { + localpart, homeserver, err = userID.Parse() + if err == nil { + err = ValidateUserLocalpart(localpart) + } + if err == nil && len(userID) > UserIDMaxLength { + err = ErrUserIDTooLong + } + return +} + +func (userID UserID) ParseAndDecode() (localpart, homeserver string, err error) { + localpart, homeserver, err = userID.ParseAndValidate() + if err == nil { + localpart, err = DecodeUserLocalpart(localpart) + } + return +} + +func (userID UserID) String() string { + return string(userID) +} + const lowerhex = "0123456789abcdef" // encode the given byte using quoted-printable encoding (e.g "=2f") @@ -116,15 +199,3 @@ func DecodeUserLocalpart(str string) (string, error) { } return outputBuffer.String(), nil } - -// ExtractUserLocalpart extracts the localpart portion of a user ID. -// See http://matrix.org/docs/spec/intro.html#user-identifiers -func ExtractUserLocalpart(userID string) (string, error) { - if len(userID) == 0 || userID[0] != '@' { - return "", fmt.Errorf("%s is not a valid user id", userID) - } - return strings.TrimPrefix( - strings.SplitN(userID, ":", 2)[0], // @foo:bar:8448 => [ "@foo", "bar:8448" ] - "@", // remove "@" prefix - ), nil -} diff --git a/vendor/maunium.net/go/mautrix/pushrules/action.go b/vendor/maunium.net/go/mautrix/pushrules/action.go new file mode 100644 index 00000000..1f885e0d --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/action.go @@ -0,0 +1,124 @@ +// 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 pushrules + +import "encoding/json" + +// PushActionType is the type of a PushAction +type PushActionType string + +// The allowed push action types as specified in spec section 11.12.1.4.1. +const ( + ActionNotify PushActionType = "notify" + ActionDontNotify PushActionType = "dont_notify" + ActionCoalesce PushActionType = "coalesce" + ActionSetTweak PushActionType = "set_tweak" +) + +// PushActionTweak is the type of the tweak in SetTweak push actions. +type PushActionTweak string + +// The allowed tweak types as specified in spec section 11.12.1.4.1.1. +const ( + TweakSound PushActionTweak = "sound" + TweakHighlight PushActionTweak = "highlight" +) + +// PushActionArray is an array of PushActions. +type PushActionArray []*PushAction + +// PushActionArrayShould contains the important information parsed from a PushActionArray. +type PushActionArrayShould struct { + // Whether or not the array contained a Notify, DontNotify or Coalesce action type. + NotifySpecified bool + // Whether or not the event in question should trigger a notification. + Notify bool + // Whether or not the event in question should be highlighted. + Highlight bool + + // Whether or not the event in question should trigger a sound alert. + PlaySound bool + // The name of the sound to play if PlaySound is true. + SoundName string +} + +// Should parses this push action array and returns the relevant details wrapped in a PushActionArrayShould struct. +func (actions PushActionArray) Should() (should PushActionArrayShould) { + for _, action := range actions { + switch action.Action { + case ActionNotify, ActionCoalesce: + should.Notify = true + should.NotifySpecified = true + case ActionDontNotify: + should.Notify = false + should.NotifySpecified = true + case ActionSetTweak: + switch action.Tweak { + case TweakHighlight: + var ok bool + should.Highlight, ok = action.Value.(bool) + if !ok { + // Highlight value not specified, so assume true since the tweak is set. + should.Highlight = true + } + case TweakSound: + should.SoundName = action.Value.(string) + should.PlaySound = len(should.SoundName) > 0 + } + } + } + return +} + +// PushAction is a single action that should be triggered when receiving a message. +type PushAction struct { + Action PushActionType + Tweak PushActionTweak + Value interface{} +} + +// UnmarshalJSON parses JSON into this PushAction. +// +// * If the JSON is a single string, the value is stored in the Action field. +// * If the JSON is an object with the set_tweak field, Action will be set to +// "set_tweak", Tweak will be set to the value of the set_tweak field and +// and Value will be set to the value of the value field. +// * In any other case, the function does nothing. +func (action *PushAction) UnmarshalJSON(raw []byte) error { + var data interface{} + + err := json.Unmarshal(raw, &data) + if err != nil { + return err + } + + switch val := data.(type) { + case string: + action.Action = PushActionType(val) + case map[string]interface{}: + tweak, ok := val["set_tweak"].(string) + if ok { + action.Action = ActionSetTweak + action.Tweak = PushActionTweak(tweak) + action.Value, _ = val["value"] + } + } + return nil +} + +// MarshalJSON is the reverse of UnmarshalJSON() +func (action *PushAction) MarshalJSON() (raw []byte, err error) { + if action.Action == ActionSetTweak { + data := map[string]interface{}{ + "set_tweak": action.Tweak, + "value": action.Value, + } + return json.Marshal(&data) + } + data := string(action.Action) + return json.Marshal(&data) +} diff --git a/vendor/maunium.net/go/mautrix/pushrules/condition.go b/vendor/maunium.net/go/mautrix/pushrules/condition.go new file mode 100644 index 00000000..f11d2b8c --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/condition.go @@ -0,0 +1,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 pushrules + +import ( + "regexp" + "strconv" + "strings" + "unicode" + + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/pushrules/glob" +) + +// Room is an interface with the functions that are needed for processing room-specific push conditions +type Room interface { + GetOwnDisplayname() string + GetMemberCount() int +} + +// PushCondKind is the type of a push condition. +type PushCondKind string + +// The allowed push condition kinds as specified in section 11.12.1.4.3 of r0.3.0 of the Client-Server API. +const ( + KindEventMatch PushCondKind = "event_match" + KindContainsDisplayName PushCondKind = "contains_display_name" + KindRoomMemberCount PushCondKind = "room_member_count" +) + +// PushCondition wraps a condition that is required for a specific PushRule to be used. +type PushCondition struct { + // The type of the condition. + Kind PushCondKind `json:"kind"` + // The dot-separated field of the event to match. Only applicable if kind is EventMatch. + Key string `json:"key,omitempty"` + // The glob-style pattern to match the field against. Only applicable if kind is EventMatch. + Pattern string `json:"pattern,omitempty"` + // The condition that needs to be fulfilled for RoomMemberCount-type conditions. + // A decimal integer optionally prefixed by ==, <, >, >= or <=. Prefix "==" is assumed if no prefix found. + MemberCountCondition string `json:"is,omitempty"` +} + +// MemberCountFilterRegex is the regular expression to parse the MemberCountCondition of PushConditions. +var MemberCountFilterRegex = regexp.MustCompile("^(==|[<>]=?)?([0-9]+)$") + +// Match checks if this condition is fulfilled for the given event in the given room. +func (cond *PushCondition) Match(room Room, evt *event.Event) bool { + switch cond.Kind { + case KindEventMatch: + return cond.matchValue(room, evt) + case KindContainsDisplayName: + return cond.matchDisplayName(room, evt) + case KindRoomMemberCount: + return cond.matchMemberCount(room) + default: + return false + } +} + +func (cond *PushCondition) matchValue(room Room, evt *event.Event) bool { + index := strings.IndexRune(cond.Key, '.') + key := cond.Key + subkey := "" + if index > 0 { + subkey = key[index+1:] + key = key[0:index] + } + + pattern, err := glob.Compile(cond.Pattern) + if err != nil { + return false + } + + switch key { + case "type": + return pattern.MatchString(evt.Type.String()) + case "sender": + return pattern.MatchString(string(evt.Sender)) + case "room_id": + return pattern.MatchString(string(evt.RoomID)) + case "state_key": + if evt.StateKey == nil { + return cond.Pattern == "" + } + return pattern.MatchString(*evt.StateKey) + case "content": + val, _ := evt.Content.Raw[subkey].(string) + return pattern.MatchString(val) + default: + return false + } +} + +func (cond *PushCondition) matchDisplayName(room Room, evt *event.Event) bool { + displayname := room.GetOwnDisplayname() + if len(displayname) == 0 { + return false + } + + msg, ok := evt.Content.Raw["body"].(string) + if !ok { + return false + } + + isAcceptable := func(r uint8) bool { + return unicode.IsSpace(rune(r)) || unicode.IsPunct(rune(r)) + } + length := len(displayname) + for index := strings.Index(msg, displayname); index != -1; index = strings.Index(msg, displayname) { + if (index <= 0 || isAcceptable(msg[index-1])) && (index+length >= len(msg) || isAcceptable(msg[index+length])) { + return true + } + msg = msg[index+len(displayname):] + } + return false +} + +func (cond *PushCondition) matchMemberCount(room Room) bool { + group := MemberCountFilterRegex.FindStringSubmatch(cond.MemberCountCondition) + if len(group) != 3 { + return false + } + + operator := group[1] + wantedMemberCount, _ := strconv.Atoi(group[2]) + + memberCount := room.GetMemberCount() + + switch operator { + case "==", "": + return memberCount == wantedMemberCount + case ">": + return memberCount > wantedMemberCount + case ">=": + return memberCount >= wantedMemberCount + case "<": + return memberCount < wantedMemberCount + case "<=": + return memberCount <= wantedMemberCount + default: + // Should be impossible due to regex. + return false + } +} diff --git a/vendor/maunium.net/go/mautrix/pushrules/doc.go b/vendor/maunium.net/go/mautrix/pushrules/doc.go new file mode 100644 index 00000000..19cd7745 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/doc.go @@ -0,0 +1,2 @@ +// Package pushrules contains utilities to parse push notification rules. +package pushrules diff --git a/vendor/maunium.net/go/mautrix/pushrules/glob/LICENSE b/vendor/maunium.net/go/mautrix/pushrules/glob/LICENSE new file mode 100644 index 00000000..cb00d952 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/glob/LICENSE @@ -0,0 +1,22 @@ +Glob is licensed under the MIT "Expat" License: + +Copyright (c) 2016: Zachary Yedidia. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/maunium.net/go/mautrix/pushrules/glob/README.md b/vendor/maunium.net/go/mautrix/pushrules/glob/README.md new file mode 100644 index 00000000..e2e6c649 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/glob/README.md @@ -0,0 +1,28 @@ +# String globbing in Go + +[![GoDoc](https://godoc.org/github.com/zyedidia/glob?status.svg)](http://godoc.org/github.com/zyedidia/glob) + +This package adds support for globs in Go. + +It simply converts glob expressions to regexps. I try to follow the standard defined [here](http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13). + +# Example + +```go +package main + +import "github.com/zyedidia/glob" + +func main() { + glob, err := glob.Compile("{*.go,*.c}") + if err != nil { + // Error + } + + glob.Match([]byte("test.c")) // true + glob.Match([]byte("hello.go")) // true + glob.Match([]byte("test.d")) // false +} +``` + +You can call all the same functions on a glob that you can call on a regexp. diff --git a/vendor/maunium.net/go/mautrix/pushrules/glob/glob.go b/vendor/maunium.net/go/mautrix/pushrules/glob/glob.go new file mode 100644 index 00000000..c270dbc5 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/glob/glob.go @@ -0,0 +1,108 @@ +// Package glob provides objects for matching strings with globs +package glob + +import "regexp" + +// Glob is a wrapper of *regexp.Regexp. +// It should contain a glob expression compiled into a regular expression. +type Glob struct { + *regexp.Regexp +} + +// Compile a takes a glob expression as a string and transforms it +// into a *Glob object (which is really just a regular expression) +// Compile also returns a possible error. +func Compile(pattern string) (*Glob, error) { + r, err := globToRegex(pattern) + return &Glob{r}, err +} + +func globToRegex(glob string) (*regexp.Regexp, error) { + regex := "" + inGroup := 0 + inClass := 0 + firstIndexInClass := -1 + arr := []byte(glob) + + hasGlobCharacters := false + + for i := 0; i < len(arr); i++ { + ch := arr[i] + + switch ch { + case '\\': + i++ + if i >= len(arr) { + regex += "\\" + } else { + next := arr[i] + switch next { + case ',': + // Nothing + case 'Q', 'E': + regex += "\\\\" + default: + regex += "\\" + } + regex += string(next) + } + case '*': + if inClass == 0 { + regex += ".*" + } else { + regex += "*" + } + hasGlobCharacters = true + case '?': + if inClass == 0 { + regex += "." + } else { + regex += "?" + } + hasGlobCharacters = true + case '[': + inClass++ + firstIndexInClass = i + 1 + regex += "[" + hasGlobCharacters = true + case ']': + inClass-- + regex += "]" + case '.', '(', ')', '+', '|', '^', '$', '@', '%': + if inClass == 0 || (firstIndexInClass == i && ch == '^') { + regex += "\\" + } + regex += string(ch) + hasGlobCharacters = true + case '!': + if firstIndexInClass == i { + regex += "^" + } else { + regex += "!" + } + hasGlobCharacters = true + case '{': + inGroup++ + regex += "(" + hasGlobCharacters = true + case '}': + inGroup-- + regex += ")" + case ',': + if inGroup > 0 { + regex += "|" + hasGlobCharacters = true + } else { + regex += "," + } + default: + regex += string(ch) + } + } + + if hasGlobCharacters { + return regexp.Compile("^" + regex + "$") + } else { + return regexp.Compile(regex) + } +} diff --git a/vendor/maunium.net/go/mautrix/pushrules/pushrules.go b/vendor/maunium.net/go/mautrix/pushrules/pushrules.go new file mode 100644 index 00000000..cb7d2f6c --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/pushrules.go @@ -0,0 +1,37 @@ +// 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 pushrules + +import ( + "encoding/gob" + "encoding/json" + "reflect" + + "maunium.net/go/mautrix/event" +) + +// EventContent represents the content of a m.push_rules account data event. +// https://matrix.org/docs/spec/client_server/r0.6.0#m-push-rules +type EventContent struct { + Ruleset *PushRuleset `json:"global"` +} + +func init() { + event.TypeMap[event.AccountDataPushRules] = reflect.TypeOf(EventContent{}) + gob.Register(&EventContent{}) +} + +// EventToPushRules converts a m.push_rules event to a PushRuleset by passing the data through JSON. +func EventToPushRules(evt *event.Event) (*PushRuleset, error) { + content := &EventContent{} + err := json.Unmarshal(evt.Content.VeryRaw, content) + if err != nil { + return nil, err + } + + return content.Ruleset, nil +} diff --git a/vendor/maunium.net/go/mautrix/pushrules/rule.go b/vendor/maunium.net/go/mautrix/pushrules/rule.go new file mode 100644 index 00000000..8ce2da77 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/rule.go @@ -0,0 +1,154 @@ +// 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 pushrules + +import ( + "encoding/gob" + + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" + "maunium.net/go/mautrix/pushrules/glob" +) + +func init() { + gob.Register(PushRuleArray{}) + gob.Register(PushRuleMap{}) +} + +type PushRuleCollection interface { + GetActions(room Room, evt *event.Event) PushActionArray +} + +type PushRuleArray []*PushRule + +func (rules PushRuleArray) SetType(typ PushRuleType) PushRuleArray { + for _, rule := range rules { + rule.Type = typ + } + return rules +} + +func (rules PushRuleArray) GetActions(room Room, evt *event.Event) PushActionArray { + for _, rule := range rules { + if !rule.Match(room, evt) { + continue + } + return rule.Actions + } + return nil +} + +type PushRuleMap struct { + Map map[string]*PushRule + Type PushRuleType +} + +func (rules PushRuleArray) SetTypeAndMap(typ PushRuleType) PushRuleMap { + data := PushRuleMap{ + Map: make(map[string]*PushRule), + Type: typ, + } + for _, rule := range rules { + rule.Type = typ + data.Map[rule.RuleID] = rule + } + return data +} + +func (ruleMap PushRuleMap) GetActions(room Room, evt *event.Event) PushActionArray { + var rule *PushRule + var found bool + switch ruleMap.Type { + case RoomRule: + rule, found = ruleMap.Map[string(evt.RoomID)] + case SenderRule: + rule, found = ruleMap.Map[string(evt.Sender)] + } + if found && rule.Match(room, evt) { + return rule.Actions + } + return nil +} + +func (ruleMap PushRuleMap) Unmap() PushRuleArray { + array := make(PushRuleArray, len(ruleMap.Map)) + index := 0 + for _, rule := range ruleMap.Map { + array[index] = rule + index++ + } + return array +} + +type PushRuleType string + +const ( + OverrideRule PushRuleType = "override" + ContentRule PushRuleType = "content" + RoomRule PushRuleType = "room" + SenderRule PushRuleType = "sender" + UnderrideRule PushRuleType = "underride" +) + +type PushRule struct { + // The type of this rule. + Type PushRuleType `json:"-"` + // The ID of this rule. + // For room-specific rules and user-specific rules, this is the room or user ID (respectively) + // For other types of rules, this doesn't affect anything. + RuleID string `json:"rule_id"` + // The actions this rule should trigger when matched. + Actions PushActionArray `json:"actions"` + // Whether this is a default rule, or has been set explicitly. + Default bool `json:"default"` + // Whether or not this push rule is enabled. + Enabled bool `json:"enabled"` + // The conditions to match in order to trigger this rule. + // Only applicable to generic underride/override rules. + Conditions []*PushCondition `json:"conditions,omitempty"` + // Pattern for content-specific push rules + Pattern string `json:"pattern,omitempty"` +} + +func (rule *PushRule) Match(room Room, evt *event.Event) bool { + if !rule.Enabled { + return false + } + switch rule.Type { + case OverrideRule, UnderrideRule: + return rule.matchConditions(room, evt) + case ContentRule: + return rule.matchPattern(room, evt) + case RoomRule: + return id.RoomID(rule.RuleID) == evt.RoomID + case SenderRule: + return id.UserID(rule.RuleID) == evt.Sender + default: + return false + } +} + +func (rule *PushRule) matchConditions(room Room, evt *event.Event) bool { + for _, cond := range rule.Conditions { + if !cond.Match(room, evt) { + return false + } + } + return true +} + +func (rule *PushRule) matchPattern(room Room, evt *event.Event) bool { + pattern, err := glob.Compile(rule.Pattern) + if err != nil { + return false + } + msg, ok := evt.Content.Raw["body"].(string) + if !ok { + return false + } + return pattern.MatchString(msg) +} diff --git a/vendor/maunium.net/go/mautrix/pushrules/ruleset.go b/vendor/maunium.net/go/mautrix/pushrules/ruleset.go new file mode 100644 index 00000000..48ae1e35 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/pushrules/ruleset.go @@ -0,0 +1,88 @@ +// 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 pushrules + +import ( + "encoding/json" + + "maunium.net/go/mautrix/event" +) + +type PushRuleset struct { + Override PushRuleArray + Content PushRuleArray + Room PushRuleMap + Sender PushRuleMap + Underride PushRuleArray +} + +type rawPushRuleset struct { + Override PushRuleArray `json:"override"` + Content PushRuleArray `json:"content"` + Room PushRuleArray `json:"room"` + Sender PushRuleArray `json:"sender"` + Underride PushRuleArray `json:"underride"` +} + +// UnmarshalJSON parses JSON into this PushRuleset. +// +// For override, sender and underride push rule arrays, the type is added +// to each PushRule and the array is used as-is. +// +// For room and sender push rule arrays, the type is added to each PushRule +// and the array is converted to a map with the rule ID as the key and the +// PushRule as the value. +func (rs *PushRuleset) UnmarshalJSON(raw []byte) (err error) { + data := rawPushRuleset{} + err = json.Unmarshal(raw, &data) + if err != nil { + return + } + + rs.Override = data.Override.SetType(OverrideRule) + rs.Content = data.Content.SetType(ContentRule) + rs.Room = data.Room.SetTypeAndMap(RoomRule) + rs.Sender = data.Sender.SetTypeAndMap(SenderRule) + rs.Underride = data.Underride.SetType(UnderrideRule) + return +} + +// MarshalJSON is the reverse of UnmarshalJSON() +func (rs *PushRuleset) MarshalJSON() ([]byte, error) { + data := rawPushRuleset{ + Override: rs.Override, + Content: rs.Content, + Room: rs.Room.Unmap(), + Sender: rs.Sender.Unmap(), + Underride: rs.Underride, + } + return json.Marshal(&data) +} + +// DefaultPushActions is the value returned if none of the rule +// collections in a Ruleset match the event given to GetActions() +var DefaultPushActions = PushActionArray{&PushAction{Action: ActionDontNotify}} + +// GetActions matches the given event against all of the push rule +// collections in this push ruleset in the order of priority as +// specified in spec section 11.12.1.4. +func (rs *PushRuleset) GetActions(room Room, evt *event.Event) (match PushActionArray) { + // Add push rule collections to array in priority order + arrays := []PushRuleCollection{rs.Override, rs.Content, rs.Room, rs.Sender, rs.Underride} + // Loop until one of the push rule collections matches the room/event combo. + for _, pra := range arrays { + if pra == nil { + continue + } + if match = pra.GetActions(room, evt); match != nil { + // Match found, return it. + return + } + } + // No match found, return default actions. + return DefaultPushActions +} diff --git a/vendor/maunium.net/go/mautrix/requests.go b/vendor/maunium.net/go/mautrix/requests.go new file mode 100644 index 00000000..72cc8e6b --- /dev/null +++ b/vendor/maunium.net/go/mautrix/requests.go @@ -0,0 +1,302 @@ +package mautrix + +import ( + "encoding/json" + + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" + "maunium.net/go/mautrix/pushrules" +) + +type AuthType string + +const ( + AuthTypePassword = "m.login.password" + AuthTypeReCAPTCHA = "m.login.recaptcha" + AuthTypeOAuth2 = "m.login.oauth2" + AuthTypeSSO = "m.login.sso" + AuthTypeEmail = "m.login.email.identity" + AuthTypeMSISDN = "m.login.msisdn" + AuthTypeToken = "m.login.token" + AuthTypeDummy = "m.login.dummy" + + AuthTypeAppservice = "m.login.application_service" + AuthTypeHalfyAppservice = "uk.half-shot.msc2778.login.application_service" +) + +type IdentifierType string + +const ( + IdentifierTypeUser = "m.id.user" + IdentifierTypeThirdParty = "m.id.thirdparty" + IdentifierTypePhone = "m.id.phone" +) + +// ReqRegister is the JSON request for https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-register +type ReqRegister struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + DeviceID id.DeviceID `json:"device_id,omitempty"` + InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"` + InhibitLogin bool `json:"inhibit_login,omitempty"` + Auth interface{} `json:"auth,omitempty"` + + // Type for registration, only used for appservice user registrations + // https://matrix.org/docs/spec/application_service/r0.1.2#server-admin-style-permissions + Type AuthType `json:"type,omitempty"` +} + +type BaseAuthData struct { + Type AuthType `json:"type"` + Session string `json:"session,omitempty"` +} + +type UserIdentifier struct { + Type IdentifierType `json:"type"` + + User string `json:"user,omitempty"` + + Medium string `json:"medium,omitempty"` + Address string `json:"address,omitempty"` + + Country string `json:"country,omitempty"` + Phone string `json:"phone,omitempty"` +} + +// ReqLogin is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login +type ReqLogin struct { + Type AuthType `json:"type"` + Identifier UserIdentifier `json:"identifier"` + Password string `json:"password,omitempty"` + Token string `json:"token,omitempty"` + DeviceID id.DeviceID `json:"device_id,omitempty"` + InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"` + + // Whether or not the returned credentials should be stored in the Client + StoreCredentials bool `json:"-"` +} + +type ReqUIAuthFallback struct { + Session string `json:"session"` + User string `json:"user"` +} + +type ReqUIAuthLogin struct { + BaseAuthData + User string `json:"user"` + Password string `json:"password"` +} + +// ReqCreateRoom is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom +type ReqCreateRoom struct { + Visibility string `json:"visibility,omitempty"` + RoomAliasName string `json:"room_alias_name,omitempty"` + Name string `json:"name,omitempty"` + Topic string `json:"topic,omitempty"` + Invite []id.UserID `json:"invite,omitempty"` + Invite3PID []ReqInvite3PID `json:"invite_3pid,omitempty"` + CreationContent map[string]interface{} `json:"creation_content,omitempty"` + InitialState []*event.Event `json:"initial_state,omitempty"` + Preset string `json:"preset,omitempty"` + IsDirect bool `json:"is_direct,omitempty"` +} + +// ReqRedact is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid +type ReqRedact struct { + Reason string `json:"reason,omitempty"` + TxnID string `json:"-"` +} + +type ReqMembers struct { + At string `json:"at"` + Membership event.Membership `json:"membership,omitempty"` + NotMembership event.Membership `json:"not_membership,omitempty"` +} + +// ReqInvite3PID is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#id57 +// It is also a JSON object used in https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom +type ReqInvite3PID struct { + IDServer string `json:"id_server"` + Medium string `json:"medium"` + Address string `json:"address"` +} + +// ReqInviteUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite +type ReqInviteUser struct { + UserID id.UserID `json:"user_id"` +} + +// ReqKickUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick +type ReqKickUser struct { + Reason string `json:"reason,omitempty"` + UserID id.UserID `json:"user_id"` +} + +// ReqBanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban +type ReqBanUser struct { + Reason string `json:"reason,omitempty"` + UserID id.UserID `json:"user_id"` +} + +// ReqUnbanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban +type ReqUnbanUser struct { + UserID id.UserID `json:"user_id"` +} + +// 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,omitempty"` +} + +type ReqPresence struct { + Presence event.Presence `json:"presence"` +} + +type ReqAliasCreate struct { + RoomID id.RoomID `json:"room_id"` +} + +type OneTimeKey struct { + Key id.Curve25519 `json:"key"` + IsSigned bool `json:"-"` + Signatures Signatures `json:"signatures,omitempty"` + Unsigned map[string]interface{} `json:"unsigned,omitempty"` +} + +type serializableOTK OneTimeKey + +func (otk *OneTimeKey) UnmarshalJSON(data []byte) (err error) { + if len(data) > 0 && data[0] == '"' && data[len(data)-1] == '"' { + err = json.Unmarshal(data, &otk.Key) + otk.Signatures = nil + otk.Unsigned = nil + otk.IsSigned = false + } else { + err = json.Unmarshal(data, (*serializableOTK)(otk)) + otk.IsSigned = true + } + return err +} + +func (otk *OneTimeKey) MarshalJSON() ([]byte, error) { + if !otk.IsSigned { + return json.Marshal(otk.Key) + } else { + return json.Marshal((*serializableOTK)(otk)) + } +} + +type ReqUploadKeys struct { + DeviceKeys *DeviceKeys `json:"device_keys,omitempty"` + OneTimeKeys map[id.KeyID]OneTimeKey `json:"one_time_keys"` +} + +type ReqKeysSignatures struct { + UserID id.UserID `json:"user_id"` + DeviceID id.DeviceID `json:"device_id,omitempty"` + Algorithms []id.Algorithm `json:"algorithms,omitempty"` + Usage []id.CrossSigningUsage `json:"usage,omitempty"` + Keys map[id.KeyID]string `json:"keys"` + Signatures Signatures `json:"signatures"` +} + +type ReqUploadSignatures map[id.UserID]map[string]ReqKeysSignatures + +type DeviceKeys struct { + UserID id.UserID `json:"user_id"` + DeviceID id.DeviceID `json:"device_id"` + Algorithms []id.Algorithm `json:"algorithms"` + Keys KeyMap `json:"keys"` + Signatures Signatures `json:"signatures"` + Unsigned map[string]interface{} `json:"unsigned,omitempty"` +} + +type CrossSigningKeys struct { + UserID id.UserID `json:"user_id"` + Usage []id.CrossSigningUsage `json:"usage"` + Keys map[id.KeyID]id.Ed25519 `json:"keys"` + Signatures map[id.UserID]map[id.KeyID]string `json:"signatures,omitempty"` +} + +func (csk *CrossSigningKeys) FirstKey() id.Ed25519 { + for _, key := range csk.Keys { + return key + } + return "" +} + +type UploadCrossSigningKeysReq struct { + Master CrossSigningKeys `json:"master_key"` + SelfSigning CrossSigningKeys `json:"self_signing_key"` + UserSigning CrossSigningKeys `json:"user_signing_key"` + Auth interface{} `json:"auth,omitempty"` +} + +type KeyMap map[id.DeviceKeyID]string + +func (km KeyMap) GetEd25519(deviceID id.DeviceID) id.Ed25519 { + val, ok := km[id.NewDeviceKeyID(id.KeyAlgorithmEd25519, deviceID)] + if !ok { + return "" + } + return id.Ed25519(val) +} + +func (km KeyMap) GetCurve25519(deviceID id.DeviceID) id.Curve25519 { + val, ok := km[id.NewDeviceKeyID(id.KeyAlgorithmCurve25519, deviceID)] + if !ok { + return "" + } + return id.Curve25519(val) +} + +type Signatures map[id.UserID]map[id.KeyID]string + +type ReqQueryKeys struct { + DeviceKeys DeviceKeysRequest `json:"device_keys"` + + Timeout int64 `json:"timeout,omitempty"` + Token string `json:"token,omitempty"` +} + +type DeviceKeysRequest map[id.UserID]DeviceIDList + +type DeviceIDList []id.DeviceID + +type ReqClaimKeys struct { + OneTimeKeys OneTimeKeysRequest `json:"one_time_keys"` + + Timeout int64 `json:"timeout,omitempty"` +} + +type OneTimeKeysRequest map[id.UserID]map[id.DeviceID]id.KeyAlgorithm + +type ReqSendToDevice struct { + Messages map[id.UserID]map[id.DeviceID]*event.Content `json:"messages"` +} + +// ReqDeviceInfo is the JSON request for https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-devices-deviceid +type ReqDeviceInfo struct { + DisplayName string `json:"display_name,omitempty"` +} + +// ReqDeleteDevice is the JSON request for https://matrix.org/docs/spec/client_server/r0.6.1#delete-matrix-client-r0-devices-deviceid +type ReqDeleteDevice struct { + Auth interface{} `json:"auth,omitempty"` +} + +// ReqDeleteDevices is the JSON request for https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-delete-devices +type ReqDeleteDevices struct { + Devices []id.DeviceID `json:"devices"` + Auth interface{} `json:"auth,omitempty"` +} + +type ReqPutPushRule struct { + Before string `json:"-"` + After string `json:"-"` + + Actions []pushrules.PushActionType `json:"actions"` + Conditions []pushrules.PushCondition `json:"conditions"` + Pattern string `json:"pattern"` +} diff --git a/vendor/maunium.net/go/mautrix/responses.go b/vendor/maunium.net/go/mautrix/responses.go new file mode 100644 index 00000000..b415d46b --- /dev/null +++ b/vendor/maunium.net/go/mautrix/responses.go @@ -0,0 +1,291 @@ +package mautrix + +import ( + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +// RespWhoami is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-account-whoami +type RespWhoami struct { + UserID id.UserID `json:"user_id"` + // N.B. This field is not in the spec yet, it's expected to land in r0.6.2 or r0.7.0 + DeviceID id.DeviceID `json:"device_id"` +} + +// RespCreateFilter is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter +type RespCreateFilter struct { + FilterID string `json:"filter_id"` +} + +// RespVersions is the JSON response for http://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-versions +type RespVersions struct { + Versions []string `json:"versions"` + UnstableFeatures map[string]bool `json:"unstable_features"` +} + +// RespJoinRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-join +type RespJoinRoom struct { + RoomID id.RoomID `json:"room_id"` +} + +// RespLeaveRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave +type RespLeaveRoom struct{} + +// RespForgetRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget +type RespForgetRoom struct{} + +// RespInviteUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite +type RespInviteUser struct{} + +// RespKickUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick +type RespKickUser struct{} + +// RespBanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban +type RespBanUser struct{} + +// RespUnbanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban +type RespUnbanUser struct{} + +// RespTyping is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid +type RespTyping struct{} + +// RespPresence is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-presence-userid-status +type RespPresence struct { + Presence event.Presence `json:"presence"` + LastActiveAgo int `json:"last_active_ago"` + StatusMsg string `json:"status_msg"` + CurrentlyActive bool `json:"currently_active"` +} + +// RespJoinedRooms is the JSON response for https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-joined-rooms +type RespJoinedRooms struct { + JoinedRooms []id.RoomID `json:"joined_rooms"` +} + +// RespJoinedMembers is the JSON response for https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-joined-rooms +type RespJoinedMembers struct { + Joined map[id.UserID]struct { + DisplayName *string `json:"display_name"` + AvatarURL *string `json:"avatar_url"` + } `json:"joined"` +} + +// 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.Event `json:"chunk"` + State []*event.Event `json:"state"` + End string `json:"end"` +} + +// RespSendEvent is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid +type RespSendEvent struct { + EventID id.EventID `json:"event_id"` +} + +// RespMediaUpload is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload +type RespMediaUpload struct { + ContentURI id.ContentURI `json:"content_uri"` +} + +// RespUserInteractive is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#user-interactive-authentication-api +type RespUserInteractive struct { + Flows []struct { + Stages []AuthType `json:"stages"` + } `json:"flows"` + Params map[AuthType]interface{} `json:"params"` + Session string `json:"session"` + Completed []string `json:"completed"` + + ErrCode string `json:"errcode"` + Error string `json:"error"` +} + +// HasSingleStageFlow returns true if there exists at least 1 Flow with a single stage of stageName. +func (r RespUserInteractive) HasSingleStageFlow(stageName AuthType) bool { + for _, f := range r.Flows { + if len(f.Stages) == 1 && f.Stages[0] == stageName { + return true + } + } + return false +} + +// RespUserDisplayName is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname +type RespUserDisplayName struct { + DisplayName string `json:"displayname"` +} + +// RespRegister is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register +type RespRegister struct { + AccessToken string `json:"access_token"` + DeviceID id.DeviceID `json:"device_id"` + HomeServer string `json:"home_server"` + RefreshToken string `json:"refresh_token"` + UserID id.UserID `json:"user_id"` +} + +type RespLoginFlows struct { + Flows []struct { + Type AuthType `json:"type"` + } `json:"flows"` +} + +func (rlf *RespLoginFlows) HasFlow(flowType AuthType) bool { + for _, flow := range rlf.Flows { + if flow.Type == flowType { + return true + } + } + return false +} + +// RespLogin is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login +type RespLogin struct { + AccessToken string `json:"access_token"` + DeviceID id.DeviceID `json:"device_id"` + UserID id.UserID `json:"user_id"` + // TODO add .well-known field here +} + +// RespLogout is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout +type RespLogout struct{} + +// RespCreateRoom is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.0.html#post-matrix-client-r0-createroom +type RespCreateRoom struct { + RoomID id.RoomID `json:"room_id"` +} + +type RespMembers struct { + Chunk []*event.Event `json:"chunk"` +} + +type LazyLoadSummary struct { + Heroes []id.UserID `json:"m.heroes,omitempty"` + JoinedMemberCount *int `json:"m.joined_member_count,omitempty"` + InvitedMemberCount *int `json:"m.invited_member_count,omitempty"` +} + +// RespSync is the JSON response for http://matrix.org/docs/spec/client_server/r0.6.0.html#get-matrix-client-r0-sync +type RespSync struct { + NextBatch string `json:"next_batch"` + + AccountData struct { + Events []*event.Event `json:"events"` + } `json:"account_data"` + Presence struct { + Events []*event.Event `json:"events"` + } `json:"presence"` + ToDevice struct { + Events []*event.Event `json:"events"` + } `json:"to_device"` + + DeviceLists struct { + Changed []id.UserID `json:"changed"` + Left []id.UserID `json:"left"` + } `json:"device_lists"` + DeviceOneTimeKeysCount OneTimeKeysCount `json:"device_one_time_keys_count"` + + Rooms struct { + Leave map[id.RoomID]SyncLeftRoom `json:"leave"` + Join map[id.RoomID]SyncJoinedRoom `json:"join"` + Invite map[id.RoomID]SyncInvitedRoom `json:"invite"` + } `json:"rooms"` +} + +type OneTimeKeysCount struct { + Curve25519 int `json:"curve25519"` + SignedCurve25519 int `json:"signed_curve25519"` +} + +type SyncLeftRoom struct { + Summary LazyLoadSummary `json:"summary"` + State struct { + Events []*event.Event `json:"events"` + } `json:"state"` + Timeline struct { + Events []*event.Event `json:"events"` + Limited bool `json:"limited"` + PrevBatch string `json:"prev_batch"` + } `json:"timeline"` +} + +type SyncJoinedRoom struct { + Summary LazyLoadSummary `json:"summary"` + State struct { + Events []*event.Event `json:"events"` + } `json:"state"` + Timeline struct { + Events []*event.Event `json:"events"` + Limited bool `json:"limited"` + PrevBatch string `json:"prev_batch"` + } `json:"timeline"` + Ephemeral struct { + Events []*event.Event `json:"events"` + } `json:"ephemeral"` + AccountData struct { + Events []*event.Event `json:"events"` + } `json:"account_data"` +} + +type SyncInvitedRoom struct { + Summary LazyLoadSummary `json:"summary"` + State struct { + Events []*event.Event `json:"events"` + } `json:"invite_state"` +} + +type RespTurnServer struct { + Username string `json:"username"` + Password string `json:"password"` + TTL int `json:"ttl"` + URIs []string `json:"uris"` +} + +type RespAliasCreate struct{} +type RespAliasDelete struct{} +type RespAliasResolve struct { + RoomID id.RoomID `json:"room_id"` + Servers []string `json:"servers"` +} + +type RespUploadKeys struct { + OneTimeKeyCounts OneTimeKeysCount `json:"one_time_key_counts"` +} + +type RespQueryKeys struct { + Failures map[string]interface{} `json:"failures"` + DeviceKeys map[id.UserID]map[id.DeviceID]DeviceKeys `json:"device_keys"` + MasterKeys map[id.UserID]CrossSigningKeys `json:"master_keys"` + SelfSigningKeys map[id.UserID]CrossSigningKeys `json:"self_signing_keys"` + UserSigningKeys map[id.UserID]CrossSigningKeys `json:"user_signing_keys"` +} + +type RespClaimKeys struct { + Failures map[string]interface{} `json:"failures"` + OneTimeKeys map[id.UserID]map[id.DeviceID]map[id.KeyID]OneTimeKey `json:"one_time_keys"` +} + +type RespUploadSignatures struct { + Failures map[string]interface{} `json:"failures"` +} + +type RespKeyChanges struct { + Changed []id.UserID `json:"changed"` + Left []id.UserID `json:"left"` +} + +type RespSendToDevice struct{} + +// RespDevicesInfo is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-devices +type RespDevicesInfo struct { + Devices []RespDeviceInfo `json:"devices"` +} + +// RespDeviceInfo is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-devices-deviceid +type RespDeviceInfo struct { + DeviceID id.DeviceID `json:"device_id"` + DisplayName string `json:"display_name"` + LastSeenIP string `json:"last_seen_ip"` + LastSeenTS int64 `json:"last_seen_ts"` +} diff --git a/vendor/maunium.net/go/mautrix/room.go b/vendor/maunium.net/go/mautrix/room.go new file mode 100644 index 00000000..588097f6 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/room.go @@ -0,0 +1,52 @@ +package mautrix + +import ( + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +// Room represents a single Matrix room. +type Room struct { + ID id.RoomID + State map[event.Type]map[string]*event.Event +} + +// UpdateState updates the room's current state with the given Event. This will clobber events based +// on the type/state_key combination. +func (room Room) UpdateState(evt *event.Event) { + _, exists := room.State[evt.Type] + if !exists { + room.State[evt.Type] = make(map[string]*event.Event) + } + room.State[evt.Type][*evt.StateKey] = evt +} + +// GetStateEvent returns the state event for the given type/state_key combo, or nil. +func (room Room) GetStateEvent(eventType event.Type, stateKey string) *event.Event { + stateEventMap, _ := room.State[eventType] + evt, _ := stateEventMap[stateKey] + return evt +} + +// 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 id.UserID) event.Membership { + state := event.MembershipLeave + evt := room.GetStateEvent(event.StateMember, string(userID)) + if evt != nil { + membership, ok := evt.Content.Raw["membership"].(string) + if ok { + state = event.Membership(membership) + } + } + return state +} + +// NewRoom creates a new Room with the given ID +func NewRoom(roomID id.RoomID) *Room { + // Init the State map and return a pointer to the Room + return &Room{ + ID: roomID, + State: make(map[event.Type]map[string]*event.Event), + } +} diff --git a/vendor/maunium.net/go/mautrix/store.go b/vendor/maunium.net/go/mautrix/store.go new file mode 100644 index 00000000..6fc9622c --- /dev/null +++ b/vendor/maunium.net/go/mautrix/store.go @@ -0,0 +1,162 @@ +package mautrix + +import ( + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +// Storer is an interface which must be satisfied to store client data. +// +// You can either write a struct which persists this data to disk, or you can use the +// provided "InMemoryStore" which just keeps data around in-memory which is lost on +// restarts. +type Storer interface { + SaveFilterID(userID id.UserID, filterID string) + LoadFilterID(userID id.UserID) string + SaveNextBatch(userID id.UserID, nextBatchToken string) + LoadNextBatch(userID id.UserID) string + SaveRoom(room *Room) + LoadRoom(roomID id.RoomID) *Room +} + +// InMemoryStore implements the Storer interface. +// +// Everything is persisted in-memory as maps. It is not safe to load/save filter IDs +// or next batch tokens on any goroutine other than the syncing goroutine: the one +// which called Client.Sync(). +type InMemoryStore struct { + Filters map[id.UserID]string + NextBatch map[id.UserID]string + Rooms map[id.RoomID]*Room +} + +// SaveFilterID to memory. +func (s *InMemoryStore) SaveFilterID(userID id.UserID, filterID string) { + s.Filters[userID] = filterID +} + +// LoadFilterID from memory. +func (s *InMemoryStore) LoadFilterID(userID id.UserID) string { + return s.Filters[userID] +} + +// SaveNextBatch to memory. +func (s *InMemoryStore) SaveNextBatch(userID id.UserID, nextBatchToken string) { + s.NextBatch[userID] = nextBatchToken +} + +// LoadNextBatch from memory. +func (s *InMemoryStore) LoadNextBatch(userID id.UserID) string { + return s.NextBatch[userID] +} + +// SaveRoom to memory. +func (s *InMemoryStore) SaveRoom(room *Room) { + s.Rooms[room.ID] = room +} + +// LoadRoom from memory. +func (s *InMemoryStore) LoadRoom(roomID id.RoomID) *Room { + return s.Rooms[roomID] +} + +// UpdateState stores a state event. This can be passed to DefaultSyncer.OnEvent to keep all room state cached. +func (s *InMemoryStore) UpdateState(_ EventSource, evt *event.Event) { + if !evt.Type.IsState() { + return + } + room := s.LoadRoom(evt.RoomID) + if room == nil { + room = NewRoom(evt.RoomID) + s.SaveRoom(room) + } + room.UpdateState(evt) +} + +// NewInMemoryStore constructs a new InMemoryStore. +func NewInMemoryStore() *InMemoryStore { + return &InMemoryStore{ + Filters: make(map[id.UserID]string), + NextBatch: make(map[id.UserID]string), + Rooms: make(map[id.RoomID]*Room), + } +} + +// AccountDataStore uses account data to store the next batch token, and +// reuses the InMemoryStore for all other operations. +type AccountDataStore struct { + *InMemoryStore + eventType string + client *Client +} + +type accountData struct { + NextBatch string `json:"next_batch"` +} + +// SaveNextBatch to account data. +func (s *AccountDataStore) SaveNextBatch(userID id.UserID, nextBatchToken string) { + if userID.String() != s.client.UserID.String() { + panic("AccountDataStore must only be used with bots") + } + + data := accountData{ + NextBatch: nextBatchToken, + } + + err := s.client.SetAccountData(s.eventType, data) + if err != nil { + if s.client.Logger != nil { + s.client.Logger.Debugfln("failed to save next batch token to account data: %s", err.Error()) + } + } +} + +// LoadNextBatch from account data. +func (s *AccountDataStore) LoadNextBatch(userID id.UserID) string { + if userID.String() != s.client.UserID.String() { + panic("AccountDataStore must only be used with bots") + } + + data := &accountData{} + + err := s.client.GetAccountData(s.eventType, data) + if err != nil { + if s.client.Logger != nil { + s.client.Logger.Debugfln("failed to load next batch token to account data: %s", err.Error()) + } + return "" + } + + return data.NextBatch +} + +// NewAccountDataStore returns a new AccountDataStore, which stores +// the next_batch token as a custom event in account data in the +// homeserver. +// +// AccountDataStore is only appropriate for bots, not appservices. +// +// eventType should be a reversed DNS name like tld.domain.sub.internal and +// must be unique for a client. The data stored in it is considered internal +// and must not be modified through outside means. You should also add a filter +// for account data changes of this event type, to avoid ending up in a sync +// loop: +// +// mautrix.Filter{ +// AccountData: mautrix.FilterPart{ +// Limit: 20, +// NotTypes: []event.Type{ +// event.NewEventType(eventType), +// }, +// }, +// } +// mautrix.Client.CreateFilter(...) +// +func NewAccountDataStore(eventType string, client *Client) *AccountDataStore { + return &AccountDataStore{ + InMemoryStore: NewInMemoryStore(), + eventType: eventType, + client: client, + } +} diff --git a/vendor/maunium.net/go/mautrix/sync.go b/vendor/maunium.net/go/mautrix/sync.go new file mode 100644 index 00000000..a7453bec --- /dev/null +++ b/vendor/maunium.net/go/mautrix/sync.go @@ -0,0 +1,283 @@ +// 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 mautrix + +import ( + "fmt" + "runtime/debug" + "time" + + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +// EventSource represents the part of the sync response that an event came from. +type EventSource int + +const ( + EventSourcePresence EventSource = 1 << iota + EventSourceJoin + EventSourceInvite + EventSourceLeave + EventSourceAccountData + EventSourceTimeline + EventSourceState + EventSourceEphemeral + EventSourceToDevice +) + +func (es EventSource) String() string { + switch { + case es == EventSourcePresence: + return "presence" + case es == EventSourceAccountData: + return "user account data" + case es == EventSourceToDevice: + return "to-device" + case es&EventSourceJoin != 0: + es -= EventSourceJoin + switch es { + case EventSourceState: + return "joined state" + case EventSourceTimeline: + return "joined timeline" + case EventSourceEphemeral: + return "room ephemeral (joined)" + case EventSourceAccountData: + return "room account data (joined)" + } + case es&EventSourceInvite != 0: + es -= EventSourceInvite + switch es { + case EventSourceState: + return "invited state" + } + case es&EventSourceLeave != 0: + es -= EventSourceLeave + switch es { + case EventSourceState: + return "left state" + case EventSourceTimeline: + return "left timeline" + } + } + return fmt.Sprintf("unknown (%d)", es) +} + +// EventHandler handles a single event from a sync response. +type EventHandler func(source EventSource, evt *event.Event) + +// SyncHandler handles a whole sync response. If the return value is false, handling will be stopped completely. +type SyncHandler func(resp *RespSync, since string) bool + +// Syncer is an interface that must be satisfied in order to do /sync requests on a client. +type Syncer interface { + // Process the /sync response. The since parameter is the since= value that was used to produce the response. + // This is useful for detecting the very first sync (since=""). If an error is return, Syncing will be stopped + // permanently. + ProcessResponse(resp *RespSync, since string) error + // OnFailedSync returns either the time to wait before retrying or an error to stop syncing permanently. + OnFailedSync(res *RespSync, err error) (time.Duration, error) + // GetFilterJSON for the given user ID. NOT the filter ID. + GetFilterJSON(userID id.UserID) *Filter +} + +type ExtensibleSyncer interface { + OnSync(callback SyncHandler) + OnEvent(callback EventHandler) + OnEventType(eventType event.Type, callback EventHandler) +} + +// DefaultSyncer is the default syncing implementation. You can either write your own syncer, or selectively +// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer +// pattern to notify callers about incoming events. See DefaultSyncer.OnEventType for more information. +type DefaultSyncer struct { + // syncListeners want the whole sync response, e.g. the crypto machine + syncListeners []SyncHandler + // globalListeners want all events + globalListeners []EventHandler + // listeners want a specific event type + listeners map[event.Type][]EventHandler + // ParseEventContent determines whether or not event content should be parsed before passing to handlers. + ParseEventContent bool + // ParseErrorHandler is called when event.Content.ParseRaw returns an error. + // If it returns false, the event will not be forwarded to listeners. + ParseErrorHandler func(evt *event.Event, err error) bool +} + +var _ Syncer = (*DefaultSyncer)(nil) +var _ ExtensibleSyncer = (*DefaultSyncer)(nil) + +// NewDefaultSyncer returns an instantiated DefaultSyncer +func NewDefaultSyncer() *DefaultSyncer { + return &DefaultSyncer{ + listeners: make(map[event.Type][]EventHandler), + syncListeners: []SyncHandler{}, + globalListeners: []EventHandler{}, + ParseEventContent: true, + ParseErrorHandler: func(evt *event.Event, err error) bool { + return false + }, + } +} + +// ProcessResponse processes the /sync response in a way suitable for bots. "Suitable for bots" means a stream of +// unrepeating events. Returns a fatal error if a listener panics. +func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("ProcessResponse panicked! since=%s panic=%s\n%s", since, r, debug.Stack()) + } + }() + + for _, listener := range s.syncListeners { + if !listener(res, since) { + return + } + } + + s.processSyncEvents("", res.Presence.Events, EventSourcePresence) + s.processSyncEvents("", res.AccountData.Events, EventSourceAccountData) + + for roomID, roomData := range res.Rooms.Join { + s.processSyncEvents(roomID, roomData.State.Events, EventSourceJoin|EventSourceState) + s.processSyncEvents(roomID, roomData.Timeline.Events, EventSourceJoin|EventSourceTimeline) + s.processSyncEvents(roomID, roomData.Ephemeral.Events, EventSourceJoin|EventSourceEphemeral) + s.processSyncEvents(roomID, roomData.AccountData.Events, EventSourceJoin|EventSourceAccountData) + } + for roomID, roomData := range res.Rooms.Invite { + s.processSyncEvents(roomID, roomData.State.Events, EventSourceInvite|EventSourceState) + } + for roomID, roomData := range res.Rooms.Leave { + s.processSyncEvents(roomID, roomData.State.Events, EventSourceLeave|EventSourceState) + s.processSyncEvents(roomID, roomData.Timeline.Events, EventSourceLeave|EventSourceTimeline) + } + return +} + +func (s *DefaultSyncer) processSyncEvents(roomID id.RoomID, events []*event.Event, source EventSource) { + for _, evt := range events { + s.processSyncEvent(roomID, evt, source) + } +} + +func (s *DefaultSyncer) processSyncEvent(roomID id.RoomID, evt *event.Event, source EventSource) { + evt.RoomID = roomID + + // Ensure the type class is correct. It's safe to mutate the class since the event type is not a pointer. + // Listeners are keyed by type structs, which means only the correct class will pass. + switch { + case evt.StateKey != nil: + evt.Type.Class = event.StateEventType + case source == EventSourcePresence, source&EventSourceEphemeral != 0: + evt.Type.Class = event.EphemeralEventType + case source&EventSourceAccountData != 0: + evt.Type.Class = event.AccountDataEventType + case source == EventSourceToDevice: + evt.Type.Class = event.ToDeviceEventType + default: + evt.Type.Class = event.MessageEventType + } + + if s.ParseEventContent { + err := evt.Content.ParseRaw(evt.Type) + if err != nil && !s.ParseErrorHandler(evt, err) { + return + } + } + + s.notifyListeners(source, evt) +} + +func (s *DefaultSyncer) notifyListeners(source EventSource, evt *event.Event) { + for _, fn := range s.globalListeners { + fn(source, evt) + } + listeners, exists := s.listeners[evt.Type] + if exists { + for _, fn := range listeners { + fn(source, evt) + } + } +} + +// 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 event.Type, callback EventHandler) { + _, exists := s.listeners[eventType] + if !exists { + s.listeners[eventType] = []EventHandler{} + } + s.listeners[eventType] = append(s.listeners[eventType], callback) +} + +func (s *DefaultSyncer) OnSync(callback SyncHandler) { + s.syncListeners = append(s.syncListeners, callback) +} + +func (s *DefaultSyncer) OnEvent(callback EventHandler) { + s.globalListeners = append(s.globalListeners, callback) +} + +// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error. +func (s *DefaultSyncer) OnFailedSync(res *RespSync, err error) (time.Duration, error) { + return 10 * time.Second, nil +} + +// GetFilterJSON returns a filter with a timeline limit of 50. +func (s *DefaultSyncer) GetFilterJSON(userID id.UserID) *Filter { + return &Filter{ + Room: RoomFilter{ + Timeline: FilterPart{ + Limit: 50, + }, + }, + } +} + +// OldEventIgnorer is an utility struct for bots to ignore events from before the bot joined the room. +// Create a struct and call Register with your DefaultSyncer to register the sync handler. +type OldEventIgnorer struct { + UserID id.UserID +} + +func (oei *OldEventIgnorer) Register(syncer ExtensibleSyncer) { + syncer.OnSync(oei.DontProcessOldEvents) +} + +// DontProcessOldEvents returns true if a sync response should be processed. May modify the response to remove +// stuff that shouldn't be processed. +func (oei *OldEventIgnorer) DontProcessOldEvents(resp *RespSync, since string) bool { + if since == "" { + return false + } + // This is a horrible hack because /sync will return the most recent messages for a room + // as soon as you /join it. We do NOT want to process those events in that particular room + // because they may have already been processed (if you toggle the bot in/out of the room). + // + // Work around this by inspecting each room's timeline and seeing if an m.room.member event for us + // exists and is "join" and then discard processing that room entirely if so. + // TODO: We probably want to process messages from after the last join event in the timeline. + for roomID, roomData := range resp.Rooms.Join { + for i := len(roomData.Timeline.Events) - 1; i >= 0; i-- { + evt := roomData.Timeline.Events[i] + if evt.Type == event.StateMember && evt.GetStateKey() == string(oei.UserID) { + membership, _ := evt.Content.Raw["membership"].(string) + if membership == "join" { + _, ok := resp.Rooms.Join[roomID] + if !ok { + continue + } + delete(resp.Rooms.Join, roomID) // don't re-process messages + delete(resp.Rooms.Invite, roomID) // don't re-process invites + break + } + } + } + } + return true +} diff --git a/vendor/maunium.net/go/mautrix/version.go b/vendor/maunium.net/go/mautrix/version.go new file mode 100644 index 00000000..ac5426a4 --- /dev/null +++ b/vendor/maunium.net/go/mautrix/version.go @@ -0,0 +1,5 @@ +package mautrix + +const Version = "v0.9.14" + +var DefaultUserAgent = "mautrix-go/" + Version diff --git a/vendor/modules.txt b/vendor/modules.txt index 4a06f74f..65e54dec 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -38,6 +38,8 @@ github.com/SevereCloud/vksdk/v2/longpoll-bot github.com/SevereCloud/vksdk/v2/object # github.com/blang/semver v3.5.1+incompatible github.com/blang/semver +# github.com/btcsuite/btcutil v1.0.2 +github.com/btcsuite/btcutil/base58 # github.com/d5/tengo/v2 v2.7.0 ## explicit github.com/d5/tengo/v2 @@ -137,7 +139,6 @@ github.com/lrstanley/girc github.com/magiconair/properties # github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 ## explicit -github.com/matrix-org/gomatrix # github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20210403163225-761e8622445d ## explicit github.com/matterbridge/Rocket.Chat.Go.SDK/models @@ -325,6 +326,7 @@ golang.org/x/crypto/ed25519/internal/edwards25519 golang.org/x/crypto/hkdf golang.org/x/crypto/internal/subtle golang.org/x/crypto/nacl/secretbox +golang.org/x/crypto/pbkdf2 golang.org/x/crypto/poly1305 golang.org/x/crypto/salsa20/salsa golang.org/x/crypto/ssh @@ -445,3 +447,12 @@ layeh.com/gumble/gumble layeh.com/gumble/gumble/MumbleProto layeh.com/gumble/gumble/varint layeh.com/gumble/gumbleutil +# maunium.net/go/mautrix v0.9.14 +## explicit +maunium.net/go/mautrix +maunium.net/go/mautrix/crypto/attachment +maunium.net/go/mautrix/crypto/utils +maunium.net/go/mautrix/event +maunium.net/go/mautrix/id +maunium.net/go/mautrix/pushrules +maunium.net/go/mautrix/pushrules/glob