mirror of
https://mau.dev/mautrix/go.git
synced 2026-03-14 14:25:53 +01:00
Reorganize and break everything
This commit is contained in:
parent
1909b4fdaf
commit
8adcd0da97
17 changed files with 586 additions and 493 deletions
191
client.go
191
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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
140
events/type.go
Normal 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}
|
||||
)
|
||||
68
filter.go
68
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
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
18
id/opaque.go
Normal 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
|
||||
|
|
@ -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
|
||||
}
|
||||
46
requests.go
46
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"`
|
||||
}
|
||||
|
|
|
|||
71
responses.go
71
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 {
|
||||
}
|
||||
|
|
|
|||
25
room.go
25
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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
36
store.go
36
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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
sync.go
29
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}}}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue