diff --git a/client.go b/client.go
index 7fa8bf3f..7d5ef6e2 100644
--- a/client.go
+++ b/client.go
@@ -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
diff --git a/events.go b/events/events.go
similarity index 59%
rename from events.go
rename to events/events.go
index 8706fcba..78c2e5c5 100644
--- a/events.go
+++ b/events/events.go
@@ -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 {
diff --git a/relations.go b/events/relations.go
similarity index 87%
rename from relations.go
rename to events/relations.go
index 69b4d365..f99b3f69 100644
--- a/relations.go
+++ b/events/relations.go
@@ -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
}
diff --git a/reply.go b/events/reply.go
similarity index 83%
rename from reply.go
rename to events/reply.go
index 8e068ba8..b0a912c4 100644
--- a/reply.go
+++ b/events/reply.go
@@ -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(`^[\s\S]+?`)
@@ -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()
diff --git a/events/type.go b/events/type.go
new file mode 100644
index 00000000..2b4b10a0
--- /dev/null
+++ b/events/type.go
@@ -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}
+)
diff --git a/filter.go b/filter.go
index a3e18310..850d5d28 100644
--- a/filter.go
+++ b/filter.go
@@ -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
diff --git a/format/htmlparser.go b/format/htmlparser.go
index f7573dbe..fc58dccb 100644
--- a/format/htmlparser.go
+++ b/format/htmlparser.go
@@ -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 (
diff --git a/format/markdown.go b/format/markdown.go
index e9592319..17e79c74 100644
--- a/format/markdown.go
+++ b/format/markdown.go
@@ -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,
}
}
diff --git a/contenturi.go b/id/contenturi.go
similarity index 66%
rename from contenturi.go
rename to id/contenturi.go
index f863b731..eb56a41e 100644
--- a/contenturi.go
+++ b/id/contenturi.go
@@ -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
}
\ No newline at end of file
diff --git a/id/opaque.go b/id/opaque.go
new file mode 100644
index 00000000..acfe9765
--- /dev/null
+++ b/id/opaque.go
@@ -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 : that is used as the key in deviceid-key mappings.
+type KeyID string
\ No newline at end of file
diff --git a/userids.go b/id/userid.go
similarity index 80%
rename from userids.go
rename to id/userid.go
index cf02db5e..0fb574fb 100644
--- a/userids.go
+++ b/id/userid.go
@@ -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
-}
\ No newline at end of file
diff --git a/requests.go b/requests.go
index 81102be2..72b9127f 100644
--- a/requests.go
+++ b/requests.go
@@ -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"`
}
diff --git a/responses.go b/responses.go
index 910db5e3..10ce09ae 100644
--- a/responses.go
+++ b/responses.go
@@ -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 {
}
diff --git a/room.go b/room.go
index 086e2592..1206bdc3 100644
--- a/room.go
+++ b/room.go
@@ -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),
}
}
diff --git a/store.go b/store.go
index 774398e2..975fbde4 100644
--- a/store.go
+++ b/store.go
@@ -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),
}
}
diff --git a/sync.go b/sync.go
index c543abe5..db152278 100644
--- a/sync.go
+++ b/sync.go
@@ -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}}}`)
}
diff --git a/userids_test.go b/userids_test.go
deleted file mode 100644
index c3eb5da9..00000000
--- a/userids_test.go
+++ /dev/null
@@ -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)
- }
- }
-}