Reorganize and break everything

This commit is contained in:
Tulir Asokan 2020-04-16 16:51:32 +03:00
commit 8adcd0da97
17 changed files with 586 additions and 493 deletions

191
client.go
View file

@ -14,10 +14,12 @@ import (
"net/url"
"path"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"maunium.net/go/mautrix/events"
"maunium.net/go/mautrix/id"
)
type Logger interface {
@ -26,10 +28,10 @@ type Logger interface {
// 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.
HomeserverURL *url.URL // The base homeserver URL
Prefix []string // 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.
AccessToken string // The access_token for the client.
UserAgent string
Client *http.Client // The underlying HTTP client which will be used to make HTTP requests.
Syncer Syncer // The thing which can process /sync responses
@ -161,7 +163,7 @@ func (cli *Client) BuildURLWithQuery(urlPath []string, urlQuery map[string]strin
}
// SetCredentials sets the user ID and access token on this client instance.
func (cli *Client) SetCredentials(userID, accessToken string) {
func (cli *Client) SetCredentials(userID id.UserID, accessToken string) {
cli.AccessToken = accessToken
cli.UserID = userID
}
@ -339,7 +341,7 @@ func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{
// 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")
urlPath := cli.BuildURL("user", string(cli.UserID), "filter")
_, err = cli.MakeRequest("POST", urlPath, &filter, &resp)
return
}
@ -366,9 +368,9 @@ func (cli *Client) SyncRequest(timeout int, since, filterID string, fullState bo
return
}
func (cli *Client) register(u string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) {
func (cli *Client) register(url string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) {
var bodyBytes []byte
bodyBytes, err = cli.MakeRequest("POST", u, req, nil)
bodyBytes, err = cli.MakeRequest("POST", url, req, nil)
if err != nil {
httpErr, ok := err.(HTTPError)
if !ok { // network error
@ -497,14 +499,14 @@ func (cli *Client) GetDisplayName(mxid string) (resp *RespUserDisplayName, err e
// 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")
urlPath := cli.BuildURL("profile", string(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")
urlPath := cli.BuildURL("profile", string(cli.UserID), "displayname")
s := struct {
DisplayName string `json:"displayname"`
}{displayName}
@ -514,7 +516,7 @@ func (cli *Client) SetDisplayName(displayName string) (err error) {
// 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() (url string, err error) {
urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url")
urlPath := cli.BuildURL("profile", string(cli.UserID), "avatar_url")
s := struct {
AvatarURL string `json:"avatar_url"`
}{}
@ -528,10 +530,10 @@ func (cli *Client) GetAvatarURL() (url string, err error) {
}
// 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) (err error) {
urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url")
func (cli *Client) SetAvatarURL(url id.ContentURI) (err error) {
urlPath := cli.BuildURL("profile", string(cli.UserID), "avatar_url")
s := struct {
AvatarURL string `json:"avatar_url"`
AvatarURL id.ContentURI `json:"avatar_url"`
}{url}
_, err = cli.MakeRequest("PUT", urlPath, &s, nil)
if err != nil {
@ -546,12 +548,12 @@ type ReqSendEvent struct {
TransactionID string
ParentID string
RelType RelationType
RelType events.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 string, eventType EventType, contentJSON interface{}, extra ...ReqSendEvent) (resp *RespSendEvent, err error) {
func (cli *Client) SendMessageEvent(roomID id.RoomID, eventType events.Type, contentJSON interface{}, extra ...ReqSendEvent) (resp *RespSendEvent, err error) {
var req ReqSendEvent
if len(extra) > 0 {
req = extra[0]
@ -569,9 +571,9 @@ func (cli *Client) SendMessageEvent(roomID string, eventType EventType, contentJ
queryParams["ts"] = strconv.FormatInt(req.Timestamp, 10)
}
urlData := []string{"rooms", roomID, "send", eventType.String(), txnID}
urlData := []string{"rooms", string(roomID), "send", eventType.String(), txnID}
if len(req.ParentID) > 0 {
urlData = []string{"rooms", roomID, "send_relation", req.ParentID, string(req.RelType), eventType.String(), txnID}
urlData = []string{"rooms", string(roomID), "send_relation", req.ParentID, string(req.RelType), eventType.String(), txnID}
}
urlPath := cli.BuildURLWithQuery(urlData, queryParams)
@ -581,16 +583,16 @@ func (cli *Client) SendMessageEvent(roomID string, eventType EventType, contentJ
// 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 string, eventType EventType, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) {
urlPath := cli.BuildURL("rooms", roomID, "state", eventType.String(), stateKey)
func (cli *Client) SendStateEvent(roomID id.RoomID, eventType events.Type, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) {
urlPath := cli.BuildURL("rooms", string(roomID), "state", eventType.String(), stateKey)
_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
return
}
// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey
// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
func (cli *Client) SendMassagedStateEvent(roomID string, eventType EventType, stateKey string, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) {
urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "state", eventType.String(), stateKey}, map[string]string{
func (cli *Client) SendMassagedStateEvent(roomID id.RoomID, eventType events.Type, stateKey string, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) {
urlPath := cli.BuildURLWithQuery([]string{"rooms", string(roomID), "state", eventType.String(), stateKey}, map[string]string{
"ts": strconv.FormatInt(ts, 10),
})
_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
@ -599,54 +601,54 @@ func (cli *Client) SendMassagedStateEvent(roomID string, eventType EventType, st
// 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, EventMessage, Content{
MsgType: MsgText,
func (cli *Client) SendText(roomID id.RoomID, text string) (*RespSendEvent, error) {
return cli.SendMessageEvent(roomID, events.EventMessage, events.Content{
MsgType: events.MsgText,
Body: text,
})
}
// SendImage sends an m.room.message event into the given room with a msgtype of m.image
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-image
func (cli *Client) SendImage(roomID, body, url string) (*RespSendEvent, error) {
return cli.SendMessageEvent(roomID, EventMessage, Content{
MsgType: MsgImage,
func (cli *Client) SendImage(roomID id.RoomID, body string, url id.ContentURI) (*RespSendEvent, error) {
return cli.SendMessageEvent(roomID, events.EventMessage, events.Content{
MsgType: events.MsgImage,
Body: body,
URL: url,
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, body, url string) (*RespSendEvent, error) {
return cli.SendMessageEvent(roomID, EventMessage, Content{
MsgType: MsgVideo,
func (cli *Client) SendVideo(roomID id.RoomID, body string, url id.ContentURI) (*RespSendEvent, error) {
return cli.SendMessageEvent(roomID, events.EventMessage, events.Content{
MsgType: events.MsgVideo,
Body: body,
URL: url,
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, text string) (*RespSendEvent, error) {
return cli.SendMessageEvent(roomID, EventMessage, Content{
MsgType: MsgNotice,
func (cli *Client) SendNotice(roomID id.RoomID, text string) (*RespSendEvent, error) {
return cli.SendMessageEvent(roomID, events.EventMessage, events.Content{
MsgType: events.MsgNotice,
Body: text,
})
}
func (cli *Client) SendReaction(roomID, eventID, reaction string) (*RespSendEvent, error) {
return cli.SendMessageEvent(roomID, EventReaction, Content{
RelatesTo: &RelatesTo{
func (cli *Client) SendReaction(roomID id.RoomID, eventID id.EventID, reaction string) (*RespSendEvent, error) {
return cli.SendMessageEvent(roomID, events.EventReaction, events.Content{
RelatesTo: &events.RelatesTo{
EventID: eventID,
Type: RelAnnotation,
Type: events.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, eventID string, extra ...ReqRedact) (resp *RespSendEvent, err error) {
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]
@ -657,7 +659,7 @@ func (cli *Client) RedactEvent(roomID, eventID string, extra ...ReqRedact) (resp
} else {
txnID = cli.TxnID()
}
urlPath := cli.BuildURL("rooms", roomID, "redact", eventID, txnID)
urlPath := cli.BuildURL("rooms", string(roomID), "redact", string(eventID), txnID)
_, err = cli.MakeRequest("PUT", urlPath, req, &resp)
return
}
@ -674,65 +676,65 @@ func (cli *Client) CreateRoom(req *ReqCreateRoom) (resp *RespCreateRoom, err err
}
// 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")
func (cli *Client) LeaveRoom(roomID id.RoomID) (resp *RespLeaveRoom, err error) {
u := cli.BuildURL("rooms", string(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")
func (cli *Client) ForgetRoom(roomID id.RoomID) (resp *RespForgetRoom, err error) {
u := cli.BuildURL("rooms", string(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")
func (cli *Client) InviteUser(roomID id.RoomID, req *ReqInviteUser) (resp *RespInviteUser, err error) {
u := cli.BuildURL("rooms", string(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")
func (cli *Client) InviteUserByThirdParty(roomID id.RoomID, req *ReqInvite3PID) (resp *RespInviteUser, err error) {
u := cli.BuildURL("rooms", string(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")
func (cli *Client) KickUser(roomID id.RoomID, req *ReqKickUser) (resp *RespKickUser, err error) {
u := cli.BuildURL("rooms", string(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")
func (cli *Client) BanUser(roomID id.RoomID, req *ReqBanUser) (resp *RespBanUser, err error) {
u := cli.BuildURL("rooms", string(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")
func (cli *Client) UnbanUser(roomID id.RoomID, req *ReqUnbanUser) (resp *RespUnbanUser, err error) {
u := cli.BuildURL("rooms", string(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) {
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)
u := cli.BuildURL("rooms", string(roomID), "typing", string(cli.UserID))
_, err = cli.MakeRequest("PUT", u, req, &resp)
return
}
func (cli *Client) SetPresence(status string) (err error) {
req := ReqPresence{Presence: status}
u := cli.BuildURL("presence", cli.UserID, "status")
u := cli.BuildURL("presence", string(cli.UserID), "status")
_, err = cli.MakeRequest("PUT", u, req, nil)
return
}
@ -740,8 +742,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 string, eventType EventType, stateKey string, outContent interface{}) (err error) {
u := cli.BuildURL("rooms", roomID, "state", eventType.String(), stateKey)
func (cli *Client) StateEvent(roomID id.RoomID, eventType events.Type, stateKey string, outContent interface{}) (err error) {
u := cli.BuildURL("rooms", string(roomID), "state", eventType.String(), stateKey)
_, err = cli.MakeRequest("GET", u, nil, outContent)
return
}
@ -758,15 +760,8 @@ func (cli *Client) UploadLink(link string) (*RespMediaUpload, error) {
return cli.Upload(res.Body, res.Header.Get("Content-Type"), res.ContentLength)
}
func (cli *Client) Download(mxcURL string) (io.ReadCloser, error) {
if !strings.HasPrefix(mxcURL, "mxc://") {
return nil, errors.New("invalid Matrix content URL")
}
parts := strings.Split(mxcURL[len("mxc://"):], "/")
if len(parts) != 2 {
return nil, errors.New("invalid Matrix content URL")
}
u := cli.BuildBaseURL("_matrix", "media", "r0", "download", parts[0], parts[1])
func (cli *Client) Download(mxcURL id.ContentURI) (io.ReadCloser, error) {
u := cli.BuildBaseURL("_matrix", "media", "r0", "download", mxcURL.Homeserver, mxcURL.FileID)
resp, err := cli.Client.Get(u)
if err != nil {
return nil, err
@ -774,7 +769,7 @@ func (cli *Client) Download(mxcURL string) (io.ReadCloser, error) {
return resp.Body, nil
}
func (cli *Client) DownloadBytes(mxcURL string) ([]byte, error) {
func (cli *Client) DownloadBytes(mxcURL id.ContentURI) ([]byte, error) {
resp, err := cli.Download(mxcURL)
if err != nil {
return nil, err
@ -829,13 +824,13 @@ func (cli *Client) Upload(content io.Reader, contentType string, contentLength i
//
// 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")
func (cli *Client) JoinedMembers(roomID id.RoomID) (resp *RespJoinedMembers, err error) {
u := cli.BuildURL("rooms", string(roomID), "joined_members")
_, err = cli.MakeRequest("GET", u, nil, &resp)
return
}
func (cli *Client) Members(roomID string, req ...ReqMembers) (resp *RespMembers, err error) {
func (cli *Client) Members(roomID id.RoomID, req ...ReqMembers) (resp *RespMembers, err error) {
var extra ReqMembers
if len(req) > 0 {
extra = req[0]
@ -850,7 +845,7 @@ func (cli *Client) Members(roomID string, req ...ReqMembers) (resp *RespMembers,
if len(extra.NotMembership) > 0 {
query["not_membership"] = string(extra.NotMembership)
}
u := cli.BuildURLWithQuery([]string{"rooms", roomID, "members"}, query)
u := cli.BuildURLWithQuery([]string{"rooms", string(roomID), "members"}, query)
_, err = cli.MakeRequest("GET", u, nil, &resp)
return
}
@ -868,7 +863,7 @@ func (cli *Client) JoinedRooms() (resp *RespJoinedRooms, err error) {
// 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) {
func (cli *Client) Messages(roomID id.RoomID, from, to string, dir rune, limit int) (resp *RespMessages, err error) {
query := map[string]string{
"from": from,
"dir": string(dir),
@ -881,12 +876,12 @@ func (cli *Client) Messages(roomID, from, to string, dir rune, limit int) (resp
query["limit"] = strconv.Itoa(limit)
}
urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "messages"}, query)
urlPath := cli.BuildURLWithQuery([]string{"rooms", string(roomID), "messages"}, query)
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
return
}
func (cli *Client) GetEvent(roomID, eventID string) (resp *Event, err error) {
func (cli *Client) GetEvent(roomID, eventID string) (resp *events.Event, err error) {
urlPath := cli.BuildURL("rooms", roomID, "event", eventID)
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
return
@ -898,9 +893,9 @@ func (cli *Client) MarkRead(roomID, eventID string) (err error) {
return
}
func (cli *Client) AddTag(roomID, tag string, order float64) (err error) {
urlPath := cli.BuildURL("user", cli.UserID, "rooms", roomID, "tags", tag)
var tagData Tag
func (cli *Client) AddTag(roomID id.RoomID, tag string, order float64) (err error) {
urlPath := cli.BuildURL("user", string(cli.UserID), "rooms", string(roomID), "tags", tag)
var tagData events.Tag
if order == order {
tagData.Order = json.Number(strconv.FormatFloat(order, 'e', -1, 64))
}
@ -908,15 +903,15 @@ func (cli *Client) AddTag(roomID, tag string, order float64) (err error) {
return
}
func (cli *Client) RemoveTag(roomID, tag string) (err error) {
urlPath := cli.BuildURL("user", cli.UserID, "rooms", roomID, "tags", tag)
func (cli *Client) RemoveTag(roomID id.RoomID, tag string) (err error) {
urlPath := cli.BuildURL("user", string(cli.UserID), "rooms", string(roomID), "tags", tag)
_, err = cli.MakeRequest("DELETE", urlPath, nil, nil)
return
}
func (cli *Client) SetTags(roomID string, tags Tags) (err error ){
urlPath := cli.BuildURL("user", cli.UserID, "rooms", roomID, "account_data", "m.tag")
_, err = cli.MakeRequest("PUT", urlPath, map[string]Tags{
func (cli *Client) SetTags(roomID id.RoomID, tags events.Tags) (err error) {
urlPath := cli.BuildURL("user", string(cli.UserID), "rooms", string(roomID), "account_data", "m.tag")
_, err = cli.MakeRequest("PUT", urlPath, map[string]events.Tags{
"tags": tags,
}, nil)
return
@ -930,31 +925,37 @@ func (cli *Client) TurnServer() (resp *RespTurnServer, err error) {
return
}
func (cli *Client) CreateAlias(alias, roomID string) (resp *RespAliasCreate, err error) {
urlPath := cli.BuildURL("directory", "room", alias)
func (cli *Client) CreateAlias(alias id.RoomAlias, roomID id.RoomID) (resp *RespAliasCreate, err error) {
urlPath := cli.BuildURL("directory", "room", string(alias))
_, err = cli.MakeRequest("PUT", urlPath, &ReqAliasCreate{RoomID: roomID}, &resp)
return
}
func (cli *Client) ResolveAlias(alias string) (resp *RespAliasResolve, err error) {
urlPath := cli.BuildURL("directory", "room", alias)
func (cli *Client) ResolveAlias(alias id.RoomAlias) (resp *RespAliasResolve, err error) {
urlPath := cli.BuildURL("directory", "room", string(alias))
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
return
}
func (cli *Client) DeleteAlias(alias string) (resp *RespAliasDelete, err error) {
urlPath := cli.BuildURL("directory", "room", alias)
func (cli *Client) DeleteAlias(alias id.RoomAlias) (resp *RespAliasDelete, err error) {
urlPath := cli.BuildURL("directory", "room", string(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) TxnID() string {
txnID := atomic.AddInt32(&cli.txnID, 1)
return fmt.Sprintf("go%d%d", time.Now().UnixNano(), txnID)
return fmt.Sprintf("mautrix-go/%d/%d", time.Now().UnixNano(), txnID)
}
// NewClient creates a new Matrix Client ready for syncing
func NewClient(homeserverURL, userID, accessToken string) (*Client, error) {
func NewClient(homeserverURL string, userID id.UserID, accessToken string) (*Client, error) {
hsURL, err := url.Parse(homeserverURL)
if err != nil {
return nil, err

View file

@ -1,125 +1,18 @@
// Copyright 2019 Tulir Asokan
package mautrix
// 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 events
import (
"encoding/json"
"strconv"
"strings"
"sync"
)
type EventTypeClass int
const (
// Normal message events
MessageEventType EventTypeClass = iota
// State events
StateEventType
// Ephemeral events
EphemeralEventType
// Account data events
AccountDataEventType
// Unknown events
UnknownEventType
)
type EventType struct {
Type string
Class EventTypeClass
}
func NewEventType(name string) EventType {
evtType := EventType{Type: name}
evtType.Class = evtType.GuessClass()
return evtType
}
func (et *EventType) IsState() bool {
return et.Class == StateEventType
}
func (et *EventType) IsEphemeral() bool {
return et.Class == EphemeralEventType
}
func (et *EventType) IsAccountData() bool {
return et.Class == AccountDataEventType
}
func (et *EventType) IsCustom() bool {
return !strings.HasPrefix(et.Type, "m.")
}
func (et *EventType) GuessClass() EventTypeClass {
switch et.Type {
case StateAliases.Type, StateCanonicalAlias.Type, StateCreate.Type, StateJoinRules.Type, StateMember.Type,
StatePowerLevels.Type, StateRoomName.Type, StateRoomAvatar.Type, StateTopic.Type, StatePinnedEvents.Type,
StateTombstone.Type:
return StateEventType
case EphemeralEventReceipt.Type, EphemeralEventTyping.Type, EphemeralEventPresence.Type:
return EphemeralEventType
case AccountDataDirectChats.Type, AccountDataPushRules.Type, AccountDataRoomTags.Type:
return AccountDataEventType
case EventRedaction.Type, EventMessage.Type, EventEncrypted.Type, EventReaction.Type, EventSticker.Type:
return MessageEventType
default:
return UnknownEventType
}
}
func (et *EventType) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, &et.Type)
if err != nil {
return err
}
et.Class = et.GuessClass()
return nil
}
func (et *EventType) MarshalJSON() ([]byte, error) {
return json.Marshal(&et.Type)
}
func (et *EventType) String() string {
return et.Type
}
// State events
var (
StateAliases = EventType{"m.room.aliases", StateEventType}
StateCanonicalAlias = EventType{"m.room.canonical_alias", StateEventType}
StateCreate = EventType{"m.room.create", StateEventType}
StateJoinRules = EventType{"m.room.join_rules", StateEventType}
StateMember = EventType{"m.room.member", StateEventType}
StatePowerLevels = EventType{"m.room.power_levels", StateEventType}
StateRoomName = EventType{"m.room.name", StateEventType}
StateTopic = EventType{"m.room.topic", StateEventType}
StateRoomAvatar = EventType{"m.room.avatar", StateEventType}
StatePinnedEvents = EventType{"m.room.pinned_events", StateEventType}
StateTombstone = EventType{"m.room.tombstone", StateEventType}
)
// Message events
var (
EventRedaction = EventType{"m.room.redaction", MessageEventType}
EventMessage = EventType{"m.room.message", MessageEventType}
EventEncrypted = EventType{"m.room.encrypted", MessageEventType}
EventReaction = EventType{"m.reaction", MessageEventType}
EventSticker = EventType{"m.sticker", MessageEventType}
)
// Ephemeral events
var (
EphemeralEventReceipt = EventType{"m.receipt", EphemeralEventType}
EphemeralEventTyping = EventType{"m.typing", EphemeralEventType}
EphemeralEventPresence = EventType{"m.presence", EphemeralEventType}
)
// Account data events
var (
AccountDataDirectChats = EventType{"m.direct", AccountDataEventType}
AccountDataPushRules = EventType{"m.push_rules", AccountDataEventType}
AccountDataRoomTags = EventType{"m.tag", AccountDataEventType}
"maunium.net/go/mautrix/crypto/attachment"
"maunium.net/go/mautrix/id"
)
type MessageType string
@ -145,17 +38,15 @@ const (
// 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 EventType `json:"type"` // The event type
Timestamp int64 `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server
ID string `json:"event_id"` // The unique ID of this event
RoomID string `json:"room_id"` // The room the event was sent to. May be nil (e.g. for presence)
Content Content `json:"content"` // The JSON content of the event.
Redacts string `json:"redacts,omitempty"` // The event ID that was redacted if a m.room.redaction event
Unsigned Unsigned `json:"unsigned,omitempty"` // Unsigned content set by own homeserver.
InviteRoomState []StrippedState `json:"invite_room_state"`
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.
}
func (evt *Event) GetStateKey() string {
@ -166,51 +57,64 @@ func (evt *Event) GetStateKey() string {
}
type StrippedState struct {
Content Content `json:"content"`
Type EventType `json:"type"`
StateKey string `json:"state_key"`
Content Content `json:"content"`
Type Type `json:"type"`
StateKey string `json:"state_key"`
}
type Unsigned struct {
PrevContent *Content `json:"prev_content,omitempty"`
PrevSender string `json:"prev_sender,omitempty"`
ReplacesState string `json:"replaces_state,omitempty"`
Age int64 `json:"age,omitempty"`
TransactionID string `json:"transaction_id,omitempty"`
Relations Relations `json:"m.relations,omitempty"`
RedactedBy string `json:"redacted_by,omitempty"`
RedactedBecause *Event `json:"redacted_because,omitempty"`
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"`
}
type EncryptedFileInfo struct {
attachment.EncryptedFile
URL id.ContentURIString
}
type Content struct {
VeryRaw json.RawMessage `json:"-"`
Raw map[string]interface{} `json:"-"`
// m.room.message
MsgType MessageType `json:"msgtype,omitempty"`
Body string `json:"body,omitempty"`
Format Format `json:"format,omitempty"`
FormattedBody string `json:"formatted_body,omitempty"`
Info *FileInfo `json:"info,omitempty"`
URL string `json:"url,omitempty"`
// Membership key for easy access in m.room.member events
Membership Membership `json:"membership,omitempty"`
// media url and info
URL id.ContentURIString `json:"url,omitempty"`
Info *FileInfo `json:"info,omitempty"`
File *EncryptedFileInfo `json:"file,omitempty"`
// edits and relations
NewContent *Content `json:"m.new_content,omitempty"`
RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
*PowerLevels
// m.room.member state
Member
Aliases []string `json:"aliases,omitempty"`
Alias string `json:"alias,omitempty"`
Name string `json:"name,omitempty"`
Topic string `json:"topic,omitempty"`
// Membership key for easy access in m.room.member events
Membership Membership `json:"membership,omitempty"`
// m.room.canonical_alias state
Alias id.RoomAlias `json:"alias,omitempty"`
AltAliases []string `json:"alt_aliases,omitempty"`
// m.room.name state
Name string `json:"name,omitempty"`
// m.room.topic state
Topic string `json:"topic,omitempty"`
// m.room.tombstone state
ReplacementRoom string `json:"replacement_room,omitempty"`
RoomTags Tags `json:"tags,omitempty"`
TypingUserIDs []string `json:"user_ids,omitempty"`
// m.tag account data
RoomTags Tags `json:"tags,omitempty"`
// m.typing ephemeral
TypingUserIDs []id.UserID `json:"user_ids,omitempty"`
}
type serializableContent Content
@ -388,7 +292,7 @@ func (pl *PowerLevels) EnsureUserLevel(userID string, level int) bool {
return false
}
func (pl *PowerLevels) GetEventLevel(eventType EventType) int {
func (pl *PowerLevels) GetEventLevel(eventType Type) int {
pl.eventsLock.RLock()
defer pl.eventsLock.RUnlock()
level, ok := pl.Events[eventType.String()]
@ -401,7 +305,7 @@ func (pl *PowerLevels) GetEventLevel(eventType EventType) int {
return level
}
func (pl *PowerLevels) SetEventLevel(eventType EventType, level int) {
func (pl *PowerLevels) SetEventLevel(eventType Type, level int) {
pl.eventsLock.Lock()
defer pl.eventsLock.Unlock()
if (eventType.IsState() && level == pl.StateDefault()) || (!eventType.IsState() && level == pl.EventsDefault) {
@ -411,7 +315,7 @@ func (pl *PowerLevels) SetEventLevel(eventType EventType, level int) {
}
}
func (pl *PowerLevels) EnsureEventLevel(eventType EventType, level int) bool {
func (pl *PowerLevels) EnsureEventLevel(eventType Type, level int) bool {
existingLevel := pl.GetEventLevel(eventType)
if existingLevel != level {
pl.SetEventLevel(eventType, level)
@ -421,13 +325,14 @@ func (pl *PowerLevels) EnsureEventLevel(eventType EventType, level int) bool {
}
type FileInfo struct {
MimeType string `json:"mimetype,omitempty"`
ThumbnailInfo *FileInfo `json:"thumbnail_info,omitempty"`
ThumbnailURL string `json:"thumbnail_url,omitempty"`
Width int `json:"-"`
Height int `json:"-"`
Duration uint `json:"-"`
Size int `json:"-"`
MimeType string `json:"mimetype,omitempty"`
ThumbnailInfo *FileInfo `json:"thumbnail_info,omitempty"`
ThumbnailURL string `json:"thumbnail_url,omitempty"`
ThumbnailFile *EncryptedFileInfo `json:"thumbnail_file,omitempty"`
Width int `json:"-"`
Height int `json:"-"`
Duration uint `json:"-"`
Size int `json:"-"`
}
type serializableFileInfo struct {

View file

@ -1,8 +1,15 @@
// Copyright 2019 Tulir Asokan
package mautrix
// 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 events
import (
"encoding/json"
"maunium.net/go/mautrix/id"
)
type RelationType string
@ -15,36 +22,36 @@ const (
type RelatesTo struct {
Type RelationType
EventID string
EventID id.EventID
Key string
}
type serializableInReplyTo struct {
EventID string `json:"event_id,omitempty"`
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 string `json:"event_id,omitempty"`
EventID id.EventID `json:"event_id,omitempty"`
Key string `json:"key,omitempty"`
}
func (rel *RelatesTo) GetReplaceID() string {
func (rel *RelatesTo) GetReplaceID() id.EventID {
if rel.Type == RelReplace {
return rel.EventID
}
return ""
}
func (rel *RelatesTo) GetReferenceID() string {
func (rel *RelatesTo) GetReferenceID() id.EventID {
if rel.Type == RelReference {
return rel.EventID
}
return ""
}
func (rel *RelatesTo) GetAnnotationID() string {
func (rel *RelatesTo) GetAnnotationID() id.EventID {
if rel.Type == RelAnnotation {
return rel.EventID
}

View file

@ -1,5 +1,10 @@
// Copyright 2018 Tulir Asokan
package mautrix
// 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 events
import (
"fmt"
@ -7,6 +12,8 @@ import (
"strings"
"golang.org/x/net/html"
"maunium.net/go/mautrix/id"
)
var HTMLReplyFallbackRegex = regexp.MustCompile(`^<mx-reply>[\s\S]+?</mx-reply>`)
@ -36,7 +43,7 @@ func (content *Content) RemoveReplyFallback() {
}
}
func (content *Content) GetReplyTo() string {
func (content *Content) GetReplyTo() id.EventID {
if content.RelatesTo != nil && content.RelatesTo.Type == RelReference {
return content.RelatesTo.EventID
}
@ -64,9 +71,9 @@ func (evt *Event) GenerateReplyFallbackText() string {
senderDisplayName := evt.Sender
var fallbackText strings.Builder
fmt.Fprintf(&fallbackText, "> <%s> %s", senderDisplayName, firstLine)
_, _ = fmt.Fprintf(&fallbackText, "> <%s> %s", senderDisplayName, firstLine)
for _, line := range lines {
fmt.Fprintf(&fallbackText, "\n> %s", line)
_, _ = fmt.Fprintf(&fallbackText, "\n> %s", line)
}
fallbackText.WriteString("\n\n")
return fallbackText.String()

140
events/type.go Normal file
View file

@ -0,0 +1,140 @@
// 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 events
import (
"encoding/json"
"strings"
)
type EventTypeClass int
const (
// Normal message events
MessageEventType EventTypeClass = iota
// State events
StateEventType
// Ephemeral events
EphemeralEventType
// Account data events
AccountDataEventType
// Device-to-device events
ToDeviceEventType
// Unknown events
UnknownEventType
)
type Type struct {
Type string
Class EventTypeClass
}
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) IsCustom() bool {
return !strings.HasPrefix(et.Type, "m.")
}
func (et *Type) GuessClass() EventTypeClass {
switch et.Type {
case StateAliases.Type, StateCanonicalAlias.Type, StateCreate.Type, StateJoinRules.Type, StateMember.Type,
StatePowerLevels.Type, StateRoomName.Type, StateRoomAvatar.Type, StateTopic.Type, StatePinnedEvents.Type,
StateTombstone.Type, StateEncryption.Type:
return StateEventType
case EphemeralEventReceipt.Type, EphemeralEventTyping.Type, EphemeralEventPresence.Type:
return EphemeralEventType
case AccountDataDirectChats.Type, AccountDataPushRules.Type, AccountDataRoomTags.Type:
return AccountDataEventType
case EventRedaction.Type, EventMessage.Type, EventEncrypted.Type, EventReaction.Type, EventSticker.Type:
return MessageEventType
case ToDeviceNewDevice.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) String() string {
return et.Type
}
// 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}
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}
)
// 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}
)
// Device-to-device events
var (
ToDeviceNewDevice = Type{"m.new_device", ToDeviceEventType}
)

View file

@ -1,52 +1,52 @@
// 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 mautrix
import "errors"
import (
"errors"
"maunium.net/go/mautrix/events"
"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.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"`
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 []string `json:"not_rooms,omitempty"`
Rooms []string `json:"rooms,omitempty"`
State FilterPart `json:"state,omitempty"`
Timeline FilterPart `json:"timeline,omitempty"`
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 []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"`
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 []events.Type `json:"not_types,omitempty"`
Senders []id.UserID `json:"senders,omitempty"`
Types []events.Type `json:"types,omitempty"`
ContainsURL *bool `json:"contains_url,omitempty"`
LazyLoadMembers bool `json:"lazy_load_members,omitempty"`
IncludeRedundantMembers bool `json:"include_redundant_members,omitempty"`
@ -54,7 +54,7 @@ type FilterPart struct {
// Validate checks if the filter contains valid property values
func (filter *Filter) Validate() error {
if filter.EventFormat != "client" && filter.EventFormat != "federation" {
if filter.EventFormat != EventFormatClient && filter.EventFormat != EventFormatFederation {
return errors.New("Bad event_format value. Must be one of [\"client\", \"federation\"]")
}
return nil

View file

@ -1,4 +1,9 @@
// Copyright 2018 Tulir Asokan
// 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 format
import (

View file

@ -1,4 +1,9 @@
// Copyright 2018 Tulir Asokan
// 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 format
import (
@ -8,7 +13,7 @@ import (
"github.com/russross/blackfriday/v2"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/events"
)
type EscapingRenderer struct {
@ -36,7 +41,7 @@ var bfhtml = blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{
var Renderer = blackfriday.WithRenderer(bfhtml)
var NoHTMLRenderer = blackfriday.WithRenderer(&EscapingRenderer{bfhtml})
func RenderMarkdown(text string, allowMarkdown, allowHTML bool) mautrix.Content {
func RenderMarkdown(text string, allowMarkdown, allowHTML bool) events.Content {
htmlBody := text
if allowMarkdown {
@ -53,17 +58,17 @@ func RenderMarkdown(text string, allowMarkdown, allowHTML bool) mautrix.Content
text = HTMLToText(htmlBody)
if htmlBody != text {
return mautrix.Content{
return events.Content{
FormattedBody: htmlBody,
Format: mautrix.FormatHTML,
MsgType: mautrix.MsgText,
Format: events.FormatHTML,
MsgType: events.MsgText,
Body: text,
}
}
}
return mautrix.Content{
MsgType: mautrix.MsgText,
return events.Content{
MsgType: events.MsgText,
Body: text,
}
}

View file

@ -1,4 +1,10 @@
package mautrix
// 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 (
"errors"
@ -8,6 +14,12 @@ import (
var InvalidContentURI = errors.New("invalid Matrix content URI")
type ContentURIString string
func (uriString ContentURIString) Parse() (ContentURI, error) {
return ParseContentURI(string(uriString))
}
type ContentURI struct {
Homeserver string
FileID string
@ -42,6 +54,10 @@ func (uri *ContentURI) String() string {
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
}

18
id/opaque.go Normal file
View file

@ -0,0 +1,18 @@
// 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
// A RoomID is a string starting with ! that references a specific room.
type RoomID string
// A RoomAlias is a string starting with # that can be resolved into
type RoomAlias string
// An EventID is a string starting with $ that references a specific event.
type EventID string
// A DeviceID is an arbitrary string that references a specific device.
type DeviceID string
// A KeyID is a string usually formatted as <algorithm>:<device_id> that is used as the key in deviceid-key mappings.
type KeyID string

View file

@ -1,4 +1,10 @@
package mautrix
// 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"
@ -7,6 +13,40 @@ import (
"strings"
)
type UserID string
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)
}
// Parse parses the user ID into the localpart and server name.
// See http://matrix.org/docs/spec/intro.html#user-identifiers
func (userID UserID) Parse() (localpart, homeserver string, err error) {
if len(userID) == 0 || userID[0] != '@' || !strings.ContainsRune(string(userID), ':') {
err = fmt.Errorf("%s is not a valid user id", userID)
return
}
parts := strings.SplitN(string(userID), ":", 2)
localpart, homeserver = strings.TrimPrefix(parts[0], "@"), parts[1]
return
}
func (userID UserID) ParseAndDecode() (localpart, homeserver string, err error) {
localpart, homeserver, err = userID.Parse()
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 +156,3 @@ func DecodeUserLocalpart(str string) (string, error) {
}
return outputBuffer.String(), nil
}
// ParseUserID parses a user ID into the localpart and server name.
// See http://matrix.org/docs/spec/intro.html#user-identifiers
func ParseUserID(userID string) (localpart, homeserver string, err error) {
if len(userID) == 0 || userID[0] != '@' || !strings.ContainsRune(userID, ':') {
err = fmt.Errorf("%s is not a valid user id", userID)
return
}
parts := strings.SplitN(userID, ":", 2)
localpart, homeserver = strings.TrimPrefix(parts[0], "@"), parts[1]
return
}

View file

@ -1,11 +1,16 @@
package mautrix
import (
"maunium.net/go/mautrix/events"
"maunium.net/go/mautrix/id"
)
// 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"`
DeviceID id.DeviceID `json:"device_id,omitempty"`
InitialDeviceDisplayName string `json:"initial_device_display_name"`
Auth interface{} `json:"auth,omitempty"`
}
@ -28,7 +33,7 @@ type ReqLogin struct {
Identifier UserIdentifier `json:"identifier"`
Password string `json:"password,omitempty"`
Token string `json:"token,omitempty"`
DeviceID string `json:"device_id,omitempty"`
DeviceID id.DeviceID `json:"device_id,omitempty"`
InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"`
}
@ -38,10 +43,10 @@ type ReqCreateRoom struct {
RoomAliasName string `json:"room_alias_name,omitempty"`
Name string `json:"name,omitempty"`
Topic string `json:"topic,omitempty"`
Invite []string `json:"invite,omitempty"`
Invite []id.UserID `json:"invite,omitempty"`
Invite3PID []ReqInvite3PID `json:"invite_3pid,omitempty"`
CreationContent map[string]interface{} `json:"creation_content,omitempty"`
InitialState []*Event `json:"initial_state,omitempty"`
InitialState []*events.Event `json:"initial_state,omitempty"`
Preset string `json:"preset,omitempty"`
IsDirect bool `json:"is_direct,omitempty"`
}
@ -53,9 +58,9 @@ type ReqRedact struct {
}
type ReqMembers struct {
At string `json:"at"`
Membership Membership `json:"membership"`
NotMembership Membership `json:"not_membership"`
At string `json:"at"`
Membership events.Membership `json:"membership"`
NotMembership events.Membership `json:"not_membership"`
}
// ReqInvite3PID is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#id57
@ -68,24 +73,24 @@ type ReqInvite3PID struct {
// 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"`
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 string `json:"user_id"`
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 string `json:"user_id"`
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 string `json:"user_id"`
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
@ -99,5 +104,18 @@ type ReqPresence struct {
}
type ReqAliasCreate struct {
RoomID string `json:"room_id"`
RoomID id.RoomID `json:"room_id"`
}
type ReqUploadKeys struct {
DeviceKeys DeviceKeys `json:"device_keys,omitempty"`
OneTimeKeys map[id.KeyID]string `json:"one_time_keys"`
}
type DeviceKeys struct {
UserID id.UserID `json:"user_id"`
DeviceID id.DeviceID `json:"device_id"`
Algorithms []string `json:"algorithms"`
Keys map[id.KeyID]string `json:"keys"`
Signatures map[id.UserID]map[string]string `json:"signatures"`
}

View file

@ -2,6 +2,9 @@ package mautrix
import (
"encoding/json"
"maunium.net/go/mautrix/events"
"maunium.net/go/mautrix/id"
)
// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface.
@ -54,7 +57,7 @@ type RespTyping struct{}
// 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 []string `json:"joined_rooms"`
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
@ -67,20 +70,20 @@ type RespJoinedMembers struct {
// RespMessages is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages
type RespMessages struct {
Start string `json:"start"`
Chunk []*Event `json:"chunk"`
State []*Event `json:"state"`
End string `json:"end"`
Start string `json:"start"`
Chunk []*events.Event `json:"chunk"`
State []*events.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 string `json:"event_id"`
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 string `json:"content_uri"`
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
@ -112,11 +115,11 @@ type RespUserDisplayName struct {
// 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"`
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 {
@ -127,10 +130,10 @@ type RespLoginFlows struct {
// 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 string `json:"device_id"`
HomeServer string `json:"home_server"`
UserID string `json:"user_id"`
AccessToken string `json:"access_token"`
DeviceID id.DeviceID `json:"device_id"`
HomeServer string `json:"home_server"`
UserID id.UserID `json:"user_id"`
}
// RespLogout is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout
@ -138,30 +141,41 @@ type RespLogout 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"`
RoomID id.RoomID `json:"room_id"`
}
type RespMembers struct {
Chunk []*Event `json:"chunk"`
Chunk []*events.Event `json:"chunk"`
}
type LazyLoadSummary struct {
Heroes []string `json:"m.heroes,omitempty"`
JoinedMemberCount *int `json:"m.joined_member_count,omitempty"`
InvitedMemberCount *int `json:"m.invited_member_count,omitempty"`
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.2.0.html#get-matrix-client-r0-sync
type RespSync struct {
NextBatch string `json:"next_batch"`
NextBatch string `json:"next_batch"`
AccountData struct {
Events []json.RawMessage `json:"events"`
} `json:"account_data"`
Presence struct {
Events []json.RawMessage `json:"events"`
} `json:"presence"`
ToDevice struct {
Events []json.RawMessage `json:"events"`
} `json:"to_device"`
DeviceLists struct {
Changed []id.UserID `json:"changed"`
Left []id.UserID `json:"left"`
} `json:"device_lists"`
DeviceOneTimeKeysCount map[string]int `json:"device_one_time_keys_count"`
Rooms struct {
Leave map[string]struct {
Leave map[id.RoomID]struct {
Summary LazyLoadSummary `json:"summary"`
State struct {
Events []json.RawMessage `json:"events"`
@ -172,7 +186,7 @@ type RespSync struct {
PrevBatch string `json:"prev_batch"`
} `json:"timeline"`
} `json:"leave"`
Join map[string]struct {
Join map[id.RoomID]struct {
Summary LazyLoadSummary `json:"summary"`
State struct {
Events []json.RawMessage `json:"events"`
@ -189,7 +203,7 @@ type RespSync struct {
Events []json.RawMessage `json:"events"`
} `json:"account_data"`
} `json:"join"`
Invite map[string]struct {
Invite map[id.RoomID]struct {
Summary LazyLoadSummary `json:"summary"`
State struct {
Events []json.RawMessage `json:"events"`
@ -208,6 +222,9 @@ type RespTurnServer struct {
type RespAliasCreate struct{}
type RespAliasDelete struct{}
type RespAliasResolve struct {
RoomID string `json:"room_id"`
Servers []string `json:"servers"`
RoomID id.RoomID `json:"room_id"`
Servers []string `json:"servers"`
}
type RespUploadKeys struct {
}

25
room.go
View file

@ -1,23 +1,28 @@
package mautrix
import (
"maunium.net/go/mautrix/events"
"maunium.net/go/mautrix/id"
)
// Room represents a single Matrix room.
type Room struct {
ID string
State map[EventType]map[string]*Event
ID id.RoomID
State map[events.Type]map[string]*events.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(event *Event) {
func (room Room) UpdateState(event *events.Event) {
_, exists := room.State[event.Type]
if !exists {
room.State[event.Type] = make(map[string]*Event)
room.State[event.Type] = make(map[string]*events.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 EventType, stateKey string) *Event {
func (room Room) GetStateEvent(eventType events.Type, stateKey string) *events.Event {
stateEventMap, _ := room.State[eventType]
event, _ := stateEventMap[stateKey]
return event
@ -25,9 +30,9 @@ func (room Room) GetStateEvent(eventType EventType, stateKey string) *Event {
// GetMembershipState returns the membership state of the given user ID in this room. If there is
// no entry for this member, 'leave' is returned for consistency with left users.
func (room Room) GetMembershipState(userID string) Membership {
state := MembershipLeave
event := room.GetStateEvent(StateMember, userID)
func (room Room) GetMembershipState(userID id.UserID) events.Membership {
state := events.MembershipLeave
event := room.GetStateEvent(events.StateMember, string(userID))
if event != nil {
state = event.Content.Membership
}
@ -35,10 +40,10 @@ func (room Room) GetMembershipState(userID string) Membership {
}
// NewRoom creates a new Room with the given ID
func NewRoom(roomID string) *Room {
func NewRoom(roomID id.RoomID) *Room {
// Init the State map and return a pointer to the Room
return &Room{
ID: roomID,
State: make(map[EventType]map[string]*Event),
State: make(map[events.Type]map[string]*events.Event),
}
}

View file

@ -1,17 +1,21 @@
package mautrix
import (
"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, filterID string)
LoadFilterID(userID string) string
SaveNextBatch(userID, nextBatchToken string)
LoadNextBatch(userID string) string
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 string) *Room
LoadRoom(roomID id.RoomID) *Room
}
// InMemoryStore implements the Storer interface.
@ -20,28 +24,28 @@ type Storer interface {
// 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
Filters map[id.UserID]string
NextBatch map[id.UserID]string
Rooms map[id.RoomID]*Room
}
// SaveFilterID to memory.
func (s *InMemoryStore) SaveFilterID(userID, filterID string) {
func (s *InMemoryStore) SaveFilterID(userID id.UserID, filterID string) {
s.Filters[userID] = filterID
}
// LoadFilterID from memory.
func (s *InMemoryStore) LoadFilterID(userID string) string {
func (s *InMemoryStore) LoadFilterID(userID id.UserID) string {
return s.Filters[userID]
}
// SaveNextBatch to memory.
func (s *InMemoryStore) SaveNextBatch(userID, nextBatchToken string) {
func (s *InMemoryStore) SaveNextBatch(userID id.UserID, nextBatchToken string) {
s.NextBatch[userID] = nextBatchToken
}
// LoadNextBatch from memory.
func (s *InMemoryStore) LoadNextBatch(userID string) string {
func (s *InMemoryStore) LoadNextBatch(userID id.UserID) string {
return s.NextBatch[userID]
}
@ -51,15 +55,15 @@ func (s *InMemoryStore) SaveRoom(room *Room) {
}
// LoadRoom from memory.
func (s *InMemoryStore) LoadRoom(roomID string) *Room {
func (s *InMemoryStore) LoadRoom(roomID id.RoomID) *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),
Filters: make(map[id.UserID]string),
NextBatch: make(map[id.UserID]string),
Rooms: make(map[id.RoomID]*Room),
}
}

29
sync.go
View file

@ -6,6 +6,9 @@ import (
"os"
"runtime/debug"
"time"
"maunium.net/go/mautrix/events"
"maunium.net/go/mautrix/id"
)
// Syncer represents an interface that must be satisfied in order to do /sync requests on a client.
@ -17,32 +20,32 @@ type Syncer interface {
// 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
GetFilterJSON(userID id.UserID) 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
UserID id.UserID
Store Storer
listeners map[EventType][]OnEventListener // event type to listeners array
listeners map[events.Type][]OnEventListener // event type to listeners array
}
// OnEventListener can be used with DefaultSyncer.OnEventType to be informed of incoming events.
type OnEventListener func(*Event)
type OnEventListener func(*events.Event)
// NewDefaultSyncer returns an instantiated DefaultSyncer
func NewDefaultSyncer(userID string, store Storer) *DefaultSyncer {
func NewDefaultSyncer(userID id.UserID, store Storer) *DefaultSyncer {
return &DefaultSyncer{
UserID: userID,
Store: store,
listeners: make(map[EventType][]OnEventListener),
listeners: make(map[events.Type][]OnEventListener),
}
}
func parseEvent(roomID string, data json.RawMessage) *Event {
event := &Event{}
func parseEvent(roomID id.RoomID, data json.RawMessage) *events.Event {
event := &events.Event{}
err := json.Unmarshal(data, event)
if err != nil {
// TODO add separate handler for these
@ -106,7 +109,7 @@ func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error)
// OnEventType allows callers to be notified when there are new events for the given event type.
// There are no duplicate checks.
func (s *DefaultSyncer) OnEventType(eventType EventType, callback OnEventListener) {
func (s *DefaultSyncer) OnEventType(eventType events.Type, callback OnEventListener) {
_, exists := s.listeners[eventType]
if !exists {
s.listeners[eventType] = []OnEventListener{}
@ -132,7 +135,7 @@ func (s *DefaultSyncer) shouldProcessResponse(resp *RespSync, since string) bool
evtData := roomData.Timeline.Events[i]
// TODO this is horribly inefficient since it's also parsed in ProcessResponse
e := parseEvent(roomID, evtData)
if e != nil && e.Type == StateMember && e.GetStateKey() == s.UserID {
if e != nil && e.Type == events.StateMember && e.GetStateKey() == string(s.UserID) {
if e.Content.Membership == "join" {
_, ok := resp.Rooms.Join[roomID]
if !ok {
@ -149,7 +152,7 @@ func (s *DefaultSyncer) shouldProcessResponse(resp *RespSync, since string) bool
}
// getOrCreateRoom must only be called by the Sync() goroutine which calls ProcessResponse()
func (s *DefaultSyncer) getOrCreateRoom(roomID string) *Room {
func (s *DefaultSyncer) getOrCreateRoom(roomID id.RoomID) *Room {
room := s.Store.LoadRoom(roomID)
if room == nil { // create a new Room
room = NewRoom(roomID)
@ -158,7 +161,7 @@ func (s *DefaultSyncer) getOrCreateRoom(roomID string) *Room {
return room
}
func (s *DefaultSyncer) notifyListeners(event *Event) {
func (s *DefaultSyncer) notifyListeners(event *events.Event) {
listeners, exists := s.listeners[event.Type]
if !exists {
return
@ -174,6 +177,6 @@ func (s *DefaultSyncer) OnFailedSync(res *RespSync, err error) (time.Duration, e
}
// GetFilterJSON returns a filter with a timeline limit of 50.
func (s *DefaultSyncer) GetFilterJSON(userID string) json.RawMessage {
func (s *DefaultSyncer) GetFilterJSON(userID id.UserID) json.RawMessage {
return json.RawMessage(`{"room":{"timeline":{"limit":50}}}`)
}

View file

@ -1,86 +0,0 @@
package mautrix
import (
"testing"
)
var useridtests = []struct {
Input string
Output string
}{
{"Alph@Bet_50up", "_alph=40_bet__50up"}, // The doc example
{"abcdef", "abcdef"}, // no-op
{"i_like_pie_", "i__like__pie__"}, // double underscore escaping
{"ABCDEF", "_a_b_c_d_e_f"}, // all-caps
{"!£", "=21=c2=a3"}, // punctuation and outside ascii range (U+00A3 => c2 a3)
{"___", "______"}, // literal underscores
{"hello-world.", "hello-world."}, // allowed punctuation
{"5+5=10", "5=2b5=3d10"}, // equals sign
{"東方Project", "=e6=9d=b1=e6=96=b9_project"}, // CJK mixed
{" foo bar", "=09foo=20bar"}, // whitespace (tab and space)
}
func TestEncodeUserLocalpart(t *testing.T) {
for _, u := range useridtests {
out := EncodeUserLocalpart(u.Input)
if out != u.Output {
t.Fatalf("TestEncodeUserLocalpart(%s) => Got: %s Expected: %s", u.Input, out, u.Output)
}
}
}
func TestDecodeUserLocalpart(t *testing.T) {
for _, u := range useridtests {
in, _ := DecodeUserLocalpart(u.Output)
if in != u.Input {
t.Fatalf("TestDecodeUserLocalpart(%s) => Got: %s Expected: %s", u.Output, in, u.Input)
}
}
}
var errtests = []struct {
Input string
}{
{"foo@bar"}, // invalid character @
{"foo_5bar"}, // invalid character after _
{"foo_._-bar"}, // multiple invalid characters after _
{"foo=2Hbar"}, // invalid hex after =
{"foo=2hbar"}, // invalid hex after = (lower-case)
{"foo=======2fbar"}, // multiple invalid hex after =
{"foo=2"}, // end of string after =
{"foo_"}, // end of string after _
}
func TestDecodeUserLocalpartErrors(t *testing.T) {
for _, u := range errtests {
out, err := DecodeUserLocalpart(u.Input)
if out != "" {
t.Fatalf("TestDecodeUserLocalpartErrors(%s) => Got: %s Expected: empty string", u.Input, out)
}
if err == nil {
t.Fatalf("TestDecodeUserLocalpartErrors(%s) => Got: nil error Expected: error", u.Input)
}
}
}
var localparttests = []struct {
Input string
ExpectOutput string
}{
{"@foo:bar", "foo"},
{"@foo:bar:8448", "foo"},
{"@foo.bar:baz.quuz", "foo.bar"},
}
func TestExtractUserLocalpart(t *testing.T) {
for _, u := range localparttests {
out, err := ExtractUserLocalpart(u.Input)
if err != nil {
t.Errorf("TestExtractUserLocalpart(%s) => Error: %s", u.Input, err)
continue
}
if out != u.ExpectOutput {
t.Errorf("TestExtractUserLocalpart(%s) => Got: %s, Want %s", u.Input, out, u.ExpectOutput)
}
}
}