From 65f2aacf903e826ca449eda3ffce1ea720525939 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 26 Aug 2018 16:12:56 +0300 Subject: [PATCH] Improve power level stuff and state event message content --- client.go | 24 ++++----- events.go | 150 +++++++++++++++++++++++++++++++++++++++++++--------- requests.go | 2 +- 3 files changed, 139 insertions(+), 37 deletions(-) diff --git a/client.go b/client.go index 936fe168..186d83c2 100644 --- a/client.go +++ b/client.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "io/ioutil" + "maunium.net/go/maulogger" "net/http" "net/url" "path" @@ -17,7 +18,6 @@ import ( "strings" "sync" "time" - "maunium.net/go/maulogger" ) // Client represents a Matrix client. @@ -361,7 +361,7 @@ func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) { } } if res == nil { - return nil, fmt.Errorf("registration failed: does this server support m.login.dummy?") + return nil, fmt.Errorf("registration failed: does this server support m.login.dummy? ") } return res, nil } @@ -461,18 +461,18 @@ func (cli *Client) SetAvatarURL(url string) (err error) { // SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid // contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal. -func (cli *Client) SendMessageEvent(roomID string, eventType string, contentJSON interface{}) (resp *RespSendEvent, err error) { +func (cli *Client) SendMessageEvent(roomID string, eventType EventType, contentJSON interface{}) (resp *RespSendEvent, err error) { txnID := txnID() - urlPath := cli.BuildURL("rooms", roomID, "send", eventType, txnID) + urlPath := cli.BuildURL("rooms", roomID, "send", string(eventType), txnID) _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp) return } // SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid // contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal. -func (cli *Client) SendMassagedMessageEvent(roomID string, eventType string, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) { +func (cli *Client) SendMassagedMessageEvent(roomID string, eventType EventType, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) { txnID := txnID() - urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "send", eventType, txnID}, map[string]string{ + urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "send", string(eventType), txnID}, map[string]string{ "ts": strconv.FormatInt(ts, 10), }) _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp) @@ -481,16 +481,16 @@ func (cli *Client) SendMassagedMessageEvent(roomID string, eventType string, con // SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey // contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal. -func (cli *Client) SendStateEvent(roomID, eventType, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) { - urlPath := cli.BuildURL("rooms", roomID, "state", eventType, stateKey) +func (cli *Client) SendStateEvent(roomID string, eventType EventType, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) { + urlPath := cli.BuildURL("rooms", roomID, "state", string(eventType), 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, eventType, stateKey string, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) { - urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "state", eventType, stateKey}, map[string]string{ +func (cli *Client) SendMassagedStateEvent(roomID string, eventType EventType, stateKey string, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) { + urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "state", string(eventType), stateKey}, map[string]string{ "ts": strconv.FormatInt(ts, 10), }) _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp) @@ -621,8 +621,8 @@ func (cli *Client) SetPresence(status string) (err error) { // StateEvent gets a single state event in a room. It will attempt to JSON unmarshal into the given "outContent" struct with // the HTTP response body, or return an error. // See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey -func (cli *Client) StateEvent(roomID, eventType, stateKey string, outContent interface{}) (err error) { - u := cli.BuildURL("rooms", roomID, "state", eventType, stateKey) +func (cli *Client) StateEvent(roomID string, eventType EventType, stateKey string, outContent interface{}) (err error) { + u := cli.BuildURL("rooms", roomID, "state", string(eventType), stateKey) _, err = cli.MakeRequest("GET", u, nil, outContent) return } diff --git a/events.go b/events.go index 0ac10c35..3a1aba0a 100644 --- a/events.go +++ b/events.go @@ -2,8 +2,7 @@ package gomatrix import ( "encoding/json" - "html" - "regexp" + "sync" ) type EventType string @@ -60,6 +59,8 @@ type Event struct { Content Content `json:"content"` // The JSON content of the event. Redacts string `json:"redacts,omitempty"` // The event ID that was redacted if a m.room.redaction event Unsigned Unsigned `json:"unsigned,omitempty"` // Unsigned content set by own homeserver. + + InviteRoomState []StrippedState `json:"invite_room_state"` } func (evt *Event) GetStateKey() string { @@ -69,6 +70,12 @@ func (evt *Event) GetStateKey() string { return "" } +type StrippedState struct { + Content Content `json:"content"` + Type EventType `json:"type"` + StateKey string `json:"state_key"` +} + type Unsigned struct { PrevContent map[string]interface{} `json:"prev_content,omitempty"` PrevSender string `json:"prev_sender,omitempty"` @@ -88,9 +95,15 @@ type Content struct { Info *FileInfo `json:"info,omitempty"` URL string `json:"url,omitempty"` + // Membership key for easy access in m.room.member events Membership string `json:"membership,omitempty"` RelatesTo *RelatesTo `json:"m.relates_to,omitempty"` + + *PowerLevels + *Member + *Aliases + *CanonicalAlias } type serializableContent Content @@ -103,11 +116,26 @@ func (content *Content) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, (*serializableContent)(content)) } -func (content *Content) UnmarshalPowerLevels() (pl PowerLevels, err error) { +/*func (content *Content) UnmarshalPowerLevels() (pl PowerLevels, err error) { err = json.Unmarshal(content.VeryRaw, &pl) return } +func (content *Content) UnmarshalMember() (m Member, err error) { + err = json.Unmarshal(content.VeryRaw, &m) + return +} + +func (content *Content) UnmarshalAliases() (a Aliases, err error) { + err = json.Unmarshal(content.VeryRaw, &a) + return +} + +func (content *Content) UnmarshalCanonicalAlias() (ca CanonicalAlias, err error) { + err = json.Unmarshal(content.VeryRaw, &ca) + return +}*/ + func (content *Content) GetInfo() *FileInfo { if content.Info == nil { content.Info = &FileInfo{} @@ -115,12 +143,38 @@ func (content *Content) GetInfo() *FileInfo { return content.Info } +type Member struct { + Membership string `json:"membership,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` + Displayname string `json:"displayname,omitempty"` + ThirdPartyInvite *ThirdPartyInvite `json:"third_party_invite,omitempty"` +} + +type ThirdPartyInvite struct { + DisplayName string `json:"display_name"` + Signed struct { + Token string `json:"token"` + Signatures json.RawMessage `json:"signatures"` + MXID string `json:"mxid"` + } +} + +type Aliases struct { + Aliases []string `json:"aliases,omitempty"` +} + +type CanonicalAlias struct { + Alias string `json:"alias,omitempty"` +} + type PowerLevels struct { - Users map[string]int `json:"users"` + usersLock sync.RWMutex `json:"-"` + Users map[string]int `json:"users,omitempty"` UsersDefault int `json:"users_default,omitempty"` - Events map[string]int `json:"events"` - EventsDefault int `json:"events_default,omitempty"` + eventsLock sync.RWMutex `json:"-"` + Events map[EventType]int `json:"events,omitempty"` + EventsDefault int `json:"events_default,omitempty"` StateDefaultPtr *int `json:"state_default,omitempty"` @@ -130,41 +184,102 @@ type PowerLevels struct { RedactPtr *int `json:"redact,omitempty"` } -func (pl PowerLevels) Invite() int { +func (pl *PowerLevels) Invite() int { if pl.InvitePtr != nil { return *pl.InvitePtr } return 50 } -func (pl PowerLevels) Kick() int { +func (pl *PowerLevels) Kick() int { if pl.KickPtr != nil { return *pl.KickPtr } return 50 } -func (pl PowerLevels) Ban() int { +func (pl *PowerLevels) Ban() int { if pl.BanPtr != nil { return *pl.BanPtr } return 50 } -func (pl PowerLevels) Redact() int { +func (pl *PowerLevels) Redact() int { if pl.RedactPtr != nil { return *pl.RedactPtr } return 50 } -func (pl PowerLevels) StateDefault() int { +func (pl *PowerLevels) StateDefault() int { if pl.StateDefaultPtr != nil { return *pl.StateDefaultPtr } return 50 } +func (pl *PowerLevels) GetUserLevel(userID string) int { + pl.usersLock.RLock() + level, ok := pl.Users[userID] + pl.usersLock.RUnlock() + if !ok { + return pl.UsersDefault + } + return level +} + +func (pl *PowerLevels) SetUserLevel(userID string, level int) { + pl.usersLock.Lock() + if level == pl.UsersDefault { + delete(pl.Users, userID) + } else { + pl.Users[userID] = level + } + pl.usersLock.Unlock() +} + +func (pl *PowerLevels) EnsureUserLevel(userID string, level int) bool { + existingLevel := pl.GetUserLevel(userID) + if existingLevel != level { + pl.SetUserLevel(userID, level) + return true + } + return false +} + +func (pl *PowerLevels) GetEventLevel(eventType EventType, isState bool) int { + pl.eventsLock.RLock() + level, ok := pl.Events[eventType] + pl.eventsLock.RUnlock() + if !ok { + if isState { + return pl.StateDefault() + } + return pl.EventsDefault + } + return level +} + +func (pl *PowerLevels) SetEventLevel(eventType EventType, isState bool, level int) { + pl.eventsLock.Lock() + if (isState && level == pl.StateDefault()) || (!isState && level == pl.EventsDefault) { + delete(pl.Events, eventType) + } else { + pl.Events[eventType] = level + } + pl.eventsLock.Unlock() +} + +func (pl *PowerLevels) EnsureEventLevel(eventType EventType, isState bool, level int) bool { + existingLevel := pl.GetEventLevel(eventType, isState) + if existingLevel != level { + pl.SetEventLevel(eventType, isState, level) + return true + } + return false +} + type FileInfo struct { MimeType string `json:"mimetype,omitempty"` ThumbnailInfo *FileInfo `json:"thumbnail_info,omitempty"` @@ -191,16 +306,3 @@ type InReplyTo struct { // Not required, just for future-proofing RoomID string `json:"room_id,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 MessageType, htmlText string) Content { - return Content{ - Body: html.UnescapeString(htmlRegex.ReplaceAllLiteralString(htmlText, "")), - MsgType: msgtype, - Format: "org.matrix.custom.html", - FormattedBody: htmlText, - } -} diff --git a/requests.go b/requests.go index 6d9c0110..d8e10a6d 100644 --- a/requests.go +++ b/requests.go @@ -31,7 +31,7 @@ type ReqCreateRoom struct { Invite []string `json:"invite,omitempty"` Invite3PID []ReqInvite3PID `json:"invite_3pid,omitempty"` CreationContent map[string]interface{} `json:"creation_content,omitempty"` - InitialState []Event `json:"initial_state,omitempty"` + InitialState []*Event `json:"initial_state,omitempty"` Preset string `json:"preset,omitempty"` IsDirect bool `json:"is_direct,omitempty"` }