Improve power level stuff and state event message content

This commit is contained in:
Tulir Asokan 2018-08-26 16:12:56 +03:00
commit 65f2aacf90
3 changed files with 139 additions and 37 deletions

View file

@ -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
}

150
events.go
View file

@ -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,
}
}

View file

@ -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"`
}