From a4cf41536fc8eef743cd330b86e784079100ec6c Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Fri, 21 Feb 2020 14:27:42 +0100 Subject: [PATCH] Refactor: move all mx* functions to mxlib/client.go --- appservice/account.go | 18 +-- appservice/db.go | 10 +- appservice/matrix.go | 275 ------------------------------------------ appservice/names.go | 23 ---- appservice/server.go | 14 ++- appservice/util.go | 51 ++++++++ easybridge.jpg | Bin 0 -> 39684 bytes mxlib/client.go | 254 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 328 insertions(+), 317 deletions(-) delete mode 100644 appservice/matrix.go delete mode 100644 appservice/names.go create mode 100644 appservice/util.go create mode 100644 easybridge.jpg create mode 100644 mxlib/client.go diff --git a/appservice/account.go b/appservice/account.go index e507230..311f266 100644 --- a/appservice/account.go +++ b/appservice/account.go @@ -81,7 +81,7 @@ func (a *Account) joinedInternal(roomId RoomID) error { log.Debugf("Joined %s (%s)\n", roomId, a.MatrixUser) - err = mxRoomInvite(mx_room_id, a.MatrixUser) + err = mx.RoomInvite(mx_room_id, a.MatrixUser) if err != nil && strings.Contains(err.Error(), "already in the room") { err = nil } @@ -107,7 +107,7 @@ func (a *Account) leftInternal(roomId RoomID) error { log.Printf("Left %s (%s)\n", roomId, a.MatrixUser) - err = mxRoomKick(mx_room_id, a.MatrixUser, fmt.Sprintf("got leave room event on %s", a.Protocol)) + err = mx.RoomKick(mx_room_id, a.MatrixUser, fmt.Sprintf("got leave room event on %s", a.Protocol)) if err != nil && strings.Contains(err.Error(), "not in the room") { err = nil } @@ -130,7 +130,7 @@ func (a *Account) userInfoUpdatedInternal(user UserID, info *UserInfo) error { } if info.DisplayName != "" { - err2 := mxProfileDisplayname(mx_user_id, fmt.Sprintf("%s (%s)", info.DisplayName, a.Protocol)) + err2 := mx.ProfileDisplayname(mx_user_id, fmt.Sprintf("%s (%s)", info.DisplayName, a.Protocol)) if err2 != nil { err = err2 } @@ -167,7 +167,7 @@ func (a *Account) roomInfoUpdatedInternal(roomId RoomID, author UserID, info *Ro } if info.Topic != "" { - err2 := mxRoomTopicAs(mx_room_id, info.Topic, as_mxid) + err2 := mx.RoomTopicAs(mx_room_id, info.Topic, as_mxid) if err2 != nil { err = err2 } @@ -175,7 +175,7 @@ func (a *Account) roomInfoUpdatedInternal(roomId RoomID, author UserID, info *Ro if info.Name != "" { name := fmt.Sprintf("%s (%s)", info.Name, a.Protocol) - err2 := mxRoomNameAs(mx_room_id, name, as_mxid) + err2 := mx.RoomNameAs(mx_room_id, name, as_mxid) if err2 != nil { err = err2 } @@ -211,7 +211,7 @@ func (a *Account) eventInternal(event *Event) error { return err } - err = mxRoomInvite(mx_room_id, mx_user_id) + err = mx.RoomInvite(mx_room_id, mx_user_id) if err != nil { if strings.Contains(err.Error(), "already in the room") { err = nil @@ -219,7 +219,7 @@ func (a *Account) eventInternal(event *Event) error { return err } - return mxRoomJoinAs(mx_room_id, mx_user_id) + return mx.RoomJoinAs(mx_room_id, mx_user_id) } else if event.Type == EVENT_LEAVE { log.Printf("%s join %s %s", a.Protocol, event.Author, event.Room) mx_room_id, err := dbGetMxRoom(a.Protocol, event.Room) @@ -227,7 +227,7 @@ func (a *Account) eventInternal(event *Event) error { return err } - return mxRoomLeaveAs(mx_room_id, mx_user_id) + return mx.RoomLeaveAs(mx_room_id, mx_user_id) } else { log.Printf("%s msg %s %s", a.Protocol, event.Author, event.Room) mx_room_id := "" @@ -249,6 +249,6 @@ func (a *Account) eventInternal(event *Event) error { typ = "m.emote" } - return mxSendMessageAs(mx_room_id, typ, event.Text, mx_user_id) + return mx.SendMessageAs(mx_room_id, typ, event.Text, mx_user_id) } } diff --git a/appservice/db.go b/appservice/db.go index 6423b95..5cefd30 100644 --- a/appservice/db.go +++ b/appservice/db.go @@ -88,13 +88,13 @@ func dbGetMxRoom(protocol string, roomId connector.RoomID) (string, error) { if must_create { alias := roomAlias(protocol, roomId) // Lookup alias - mx_room_id, err := mxDirectoryRoom(fmt.Sprintf("#%s:%s", alias, config.MatrixDomain)) + mx_room_id, err := mx.DirectoryRoom(fmt.Sprintf("#%s:%s", alias, config.MatrixDomain)) // If no alias found, create room if err != nil { name := fmt.Sprintf("%s (%s)", roomId, protocol) - mx_room_id, err = mxCreateRoom(name, alias, []string{}) + mx_room_id, err = mx.CreateRoom(name, alias, []string{}) if err != nil { log.Printf("Could not create room for %s: %s", name, err) return "", err @@ -125,7 +125,7 @@ func dbGetMxPmRoom(protocol string, them connector.UserID, themMxId string, usMx if must_create { name := fmt.Sprintf("%s (%s)", them, protocol) - mx_room_id, err := mxCreateDirectRoomAs([]string{usMxId}, themMxId) + mx_room_id, err := mx.CreateDirectRoomAs([]string{usMxId}, themMxId) if err != nil { log.Printf("Could not create room for %s: %s", name, err) return "", err @@ -161,7 +161,7 @@ func dbGetMxUser(protocol string, userId connector.UserID) (string, error) { if must_create { username := userMxId(protocol, userId) - err := mxRegisterUser(username) + err := mx.RegisterUser(username) if err != nil { if mxE, ok := err.(*mxlib.MxError); !ok || mxE.ErrCode != "M_USER_IN_USE" { log.Printf("Could not register %s: %s", username, err) @@ -170,7 +170,7 @@ func dbGetMxUser(protocol string, userId connector.UserID) (string, error) { } mxid := fmt.Sprintf("@%s:%s", username, config.MatrixDomain) - mxProfileDisplayname(mxid, fmt.Sprintf("%s (%s)", userId, protocol)) + mx.ProfileDisplayname(mxid, fmt.Sprintf("%s (%s)", userId, protocol)) user = DbUserMap{ Protocol: protocol, diff --git a/appservice/matrix.go b/appservice/matrix.go deleted file mode 100644 index 6b99992..0000000 --- a/appservice/matrix.go +++ /dev/null @@ -1,275 +0,0 @@ -package appservice - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "net/url" - "time" - - log "github.com/sirupsen/logrus" - - "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector" - . "git.deuxfleurs.fr/Deuxfleurs/easybridge/mxlib" -) - -const EASYBRIDGE_SYSTEM_PROTOCOL string = "✯◡✯" - -func ezbrMxId() string { - return fmt.Sprintf("@%s:%s", registration.SenderLocalpart, config.MatrixDomain) -} - -func ezbrSystemRoom(user_mx_id string) (string, error) { - return dbGetMxPmRoom(EASYBRIDGE_SYSTEM_PROTOCOL, connector.UserID("Easybridge"), ezbrMxId(), user_mx_id, "easybridge") -} - -func ezbrSystemSend(user_mx_id string, msg string) { - mx_room_id, err := ezbrSystemRoom(user_mx_id) - if err == nil { - err = mxSendMessageAs(mx_room_id, "m.text", msg, ezbrMxId()) - } - if err != nil { - log.Warnf("(%s) %s", user_mx_id, msg) - } -} - -func ezbrSystemSendf(user_mx_id string, format string, args ...interface{}) { - ezbrSystemSend(user_mx_id, fmt.Sprintf(format, args...)) -} - -// ---- - -var httpClient *http.Client - -func init() { - tr := &http.Transport{ - MaxIdleConns: 10, - IdleConnTimeout: 30 * time.Second, - DisableCompression: true, - } - httpClient = &http.Client{Transport: tr} -} - -func mxGetApiCall(endpoint string, response interface{}) error { - log.Debugf("Matrix GET request: %s\n", endpoint) - - req, err := http.NewRequest("GET", config.Server+endpoint, nil) - if err != nil { - return err - } - - return mxDoAndParse(req, response) -} - -func mxPutApiCall(endpoint string, data interface{}, response interface{}) error { - body, err := json.Marshal(data) - if err != nil { - return err - } - - log.Debugf("Matrix PUT request: %s %s\n", endpoint, string(body)) - - req, err := http.NewRequest("PUT", config.Server+endpoint, bytes.NewBuffer(body)) - if err != nil { - return err - } - req.Header.Add("Content-Type", "application/json") - - return mxDoAndParse(req, response) -} - -func mxPostApiCall(endpoint string, data interface{}, response interface{}) error { - body, err := json.Marshal(data) - if err != nil { - return err - } - - log.Debugf("Matrix POST request: %s %s\n", endpoint, string(body)) - - req, err := http.NewRequest("POST", config.Server+endpoint, bytes.NewBuffer(body)) - if err != nil { - return err - } - req.Header.Add("Content-Type", "application/json") - - return mxDoAndParse(req, response) -} - -func mxDoAndParse(req *http.Request, response interface{}) error { - req.Header.Add("Authorization", "Bearer "+registration.AsToken) - - resp, err := httpClient.Do(req) - if err != nil { - return err - } - - if resp.StatusCode != http.StatusOK { - var e MxError - err = json.NewDecoder(resp.Body).Decode(&e) - if err != nil { - return err - } - log.Debugf("Response (%d): %#v\n", resp.StatusCode, e) - return &e - } - - err = json.NewDecoder(resp.Body).Decode(response) - if err != nil { - return err - } - - log.Debugf("Response: %#v\n", response) - return nil -} - -// ---- - -func mxRegisterUser(username string) error { - req := RegisterRequest{ - Username: username, - } - var rep RegisterResponse - return mxPostApiCall("/_matrix/client/r0/register?kind=user", &req, &rep) -} - -func mxProfileDisplayname(userid string, displayname string) error { - req := ProfileDisplaynameRequest{ - Displayname: displayname, - } - var rep struct{} - err := mxPutApiCall(fmt.Sprintf("/_matrix/client/r0/profile/%s/displayname?user_id=%s", - url.QueryEscape(userid), url.QueryEscape(userid)), - &req, &rep) - return err -} - -func mxDirectoryRoom(alias string) (string, error) { - var rep DirectoryRoomResponse - err := mxGetApiCall("/_matrix/client/r0/directory/room/"+url.QueryEscape(alias), &rep) - if err != nil { - return "", err - } - return rep.RoomId, nil -} - -func mxCreateRoom(name string, alias string, invite []string) (string, error) { - rq := CreateRoomRequest{ - Preset: "private_chat", - RoomAliasName: alias, - Name: name, - Topic: "", - Invite: invite, - CreationContent: map[string]interface{}{ - "m.federate": false, - }, - PowerLevels: map[string]interface{}{ - "invite": 100, - "events": map[string]interface{}{ - "m.room.topic": 0, - "m.room.avatar": 0, - }, - }, - } - var rep CreateRoomResponse - err := mxPostApiCall("/_matrix/client/r0/createRoom", &rq, &rep) - if err != nil { - return "", err - } - return rep.RoomId, nil -} - -func mxCreateDirectRoomAs(invite []string, as_user string) (string, error) { - rq := CreateDirectRoomRequest{ - Preset: "private_chat", - Topic: "", - Invite: invite, - CreationContent: map[string]interface{}{ - "m.federate": false, - }, - PowerLevels: map[string]interface{}{ - "invite": 100, - }, - IsDirect: true, - } - var rep CreateRoomResponse - err := mxPostApiCall("/_matrix/client/r0/createRoom?user_id="+url.QueryEscape(as_user), &rq, &rep) - if err != nil { - return "", err - } - return rep.RoomId, nil -} - -func mxRoomInvite(room string, user string) error { - rq := RoomInviteRequest{ - UserId: user, - } - var rep struct{} - err := mxPostApiCall("/_matrix/client/r0/rooms/"+url.QueryEscape(room)+"/invite", &rq, &rep) - return err -} - -func mxRoomKick(room string, user string, reason string) error { - rq := RoomKickRequest{ - UserId: user, - Reason: reason, - } - var rep struct{} - err := mxPostApiCall("/_matrix/client/r0/rooms/"+url.QueryEscape(room)+"/kick", &rq, &rep) - return err -} - -func mxRoomJoinAs(room string, user string) error { - rq := struct{}{} - var rep RoomJoinResponse - err := mxPostApiCall("/_matrix/client/r0/rooms/"+url.QueryEscape(room)+"/join?user_id="+url.QueryEscape(user), &rq, &rep) - return err -} - -func mxRoomLeaveAs(room string, user string) error { - rq := struct{}{} - var rep struct{} - err := mxPostApiCall("/_matrix/client/r0/rooms/"+url.QueryEscape(room)+"/leave?user_id="+url.QueryEscape(user), &rq, &rep) - return err -} - -func mxSendAs(room string, event_type string, content map[string]interface{}, user string) error { - txn_id := time.Now().UnixNano() - var rep RoomSendResponse - err := mxPutApiCall(fmt.Sprintf( - "/_matrix/client/r0/rooms/%s/send/%s/%d?user_id=%s", - url.QueryEscape(room), event_type, txn_id, url.QueryEscape(user)), - &content, &rep) - return err -} - -func mxSendMessageAs(room string, typ string, body string, user string) error { - content := map[string]interface{}{ - "msgtype": typ, - "body": body, - } - return mxSendAs(room, "m.room.message", content, user) -} - -func mxPutStateAs(room string, event_type string, key string, content map[string]interface{}, as_user string) error { - var rep RoomSendResponse - err := mxPutApiCall(fmt.Sprintf( - "/_matrix/client/r0/rooms/%s/state/%s/%s?user_id=%s", - url.QueryEscape(room), event_type, key, url.QueryEscape(as_user)), - &content, &rep) - return err -} - -func mxRoomNameAs(room string, name string, as_user string) error { - content := map[string]interface{}{ - "name": name, - } - return mxPutStateAs(room, "m.room.name", "", content, as_user) -} - -func mxRoomTopicAs(room string, topic string, as_user string) error { - content := map[string]interface{}{ - "topic": topic, - } - return mxPutStateAs(room, "m.room.topic", "", content, as_user) -} diff --git a/appservice/names.go b/appservice/names.go deleted file mode 100644 index 9ed00f8..0000000 --- a/appservice/names.go +++ /dev/null @@ -1,23 +0,0 @@ -package appservice - -import ( - "fmt" - "strings" - - . "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector" -) - -func roomAlias(protocol string, id RoomID) string { - return fmt.Sprintf("_ezbr__%s__%s", safeStringForId(string(id)), protocol) -} - -func userMxId(protocol string, id UserID) string { - return fmt.Sprintf("_ezbr__%s__%s", safeStringForId(string(id)), protocol) -} - -func safeStringForId(in string) string { - id2 := strings.ReplaceAll(in, "#", "") - id2 = strings.ReplaceAll(id2, "@", "__") - id2 = strings.ReplaceAll(id2, ":", "_") - return id2 -} diff --git a/appservice/server.go b/appservice/server.go index aa43935..d3ecb43 100644 --- a/appservice/server.go +++ b/appservice/server.go @@ -24,20 +24,24 @@ type Config struct { var registration *mxlib.Registration var config *Config +var mx *mxlib.Client + func Start(r *mxlib.Registration, c *Config) (chan error, error) { registration = r config = c + mx = mxlib.NewClient(c.Server, r.AsToken) + err := InitDb() if err != nil { return nil, err } - err = mxRegisterUser(registration.SenderLocalpart) + err = mx.RegisterUser(registration.SenderLocalpart) if mxe, ok := err.(*mxlib.MxError); !ok || mxe.ErrCode != "M_USER_IN_USE" { return nil, err } - err = mxProfileDisplayname(ezbrMxId(), fmt.Sprintf("Easybridge (%s)", EASYBRIDGE_SYSTEM_PROTOCOL)) + err = mx.ProfileDisplayname(ezbrMxId(), fmt.Sprintf("Easybridge (%s)", EASYBRIDGE_SYSTEM_PROTOCOL)) if err != nil { return nil, err } @@ -134,7 +138,7 @@ func handleTxnEvent(e *mxlib.Event) error { ev.Room = room.RoomID return acct.Conn.Send(ev) } else { - mxRoomKick(e.RoomId, e.Sender, fmt.Sprintf("Not present in %s on %s, please talk with Easybridge to rejoin", room.RoomID, room.Protocol)) + mx.RoomKick(e.RoomId, e.Sender, fmt.Sprintf("Not present in %s on %s, please talk with Easybridge to rejoin", room.RoomID, room.Protocol)) return fmt.Errorf("not joined %s on %s", room.RoomID, room.Protocol) } } else { @@ -146,7 +150,7 @@ func handleTxnEvent(e *mxlib.Event) error { if pm_room := dbIsPmRoom(e.RoomId); pm_room != nil { // If leaving a PM room, we must delete it them_mx := userMxId(pm_room.Protocol, pm_room.UserID) - mxRoomLeaveAs(e.RoomId, them_mx) + mx.RoomLeaveAs(e.RoomId, them_mx) db.Delete(pm_room) return nil } else if room := dbIsPublicRoom(e.RoomId); room != nil { @@ -157,7 +161,7 @@ func handleTxnEvent(e *mxlib.Event) error { return nil // TODO: manage autojoin list, remove this room } else { - mxRoomKick(e.RoomId, e.Sender, fmt.Sprintf("Not present in %s on %s, please talk with Easybridge to rejoin", room.RoomID, room.Protocol)) + mx.RoomKick(e.RoomId, e.Sender, fmt.Sprintf("Not present in %s on %s, please talk with Easybridge to rejoin", room.RoomID, room.Protocol)) return fmt.Errorf("not joined %s on %s", room.RoomID, room.Protocol) } } else { diff --git a/appservice/util.go b/appservice/util.go new file mode 100644 index 0000000..4175fb0 --- /dev/null +++ b/appservice/util.go @@ -0,0 +1,51 @@ +package appservice + +import ( + "fmt" + "strings" + + log "github.com/sirupsen/logrus" + + . "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector" +) + +const EASYBRIDGE_SYSTEM_PROTOCOL string = "✯◡✯" + +func ezbrMxId() string { + return fmt.Sprintf("@%s:%s", registration.SenderLocalpart, config.MatrixDomain) +} + +func ezbrSystemRoom(user_mx_id string) (string, error) { + return dbGetMxPmRoom(EASYBRIDGE_SYSTEM_PROTOCOL, UserID("Easybridge"), ezbrMxId(), user_mx_id, "easybridge") +} + +func ezbrSystemSend(user_mx_id string, msg string) { + mx_room_id, err := ezbrSystemRoom(user_mx_id) + if err == nil { + err = mx.SendMessageAs(mx_room_id, "m.text", msg, ezbrMxId()) + } + if err != nil { + log.Warnf("(%s) %s", user_mx_id, msg) + } +} + +func ezbrSystemSendf(user_mx_id string, format string, args ...interface{}) { + ezbrSystemSend(user_mx_id, fmt.Sprintf(format, args...)) +} + +// ---- + +func roomAlias(protocol string, id RoomID) string { + return fmt.Sprintf("_ezbr__%s__%s", safeStringForId(string(id)), protocol) +} + +func userMxId(protocol string, id UserID) string { + return fmt.Sprintf("_ezbr__%s__%s", safeStringForId(string(id)), protocol) +} + +func safeStringForId(in string) string { + id2 := strings.ReplaceAll(in, "#", "") + id2 = strings.ReplaceAll(id2, "@", "__") + id2 = strings.ReplaceAll(id2, ":", "_") + return id2 +} diff --git a/easybridge.jpg b/easybridge.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b9d83a88869fd1c3843e023df1837fec69cec990 GIT binary patch literal 39684 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 Kr4<$0fB)I@Os;wW literal 0 HcmV?d00001 diff --git a/mxlib/client.go b/mxlib/client.go new file mode 100644 index 0000000..2385d6e --- /dev/null +++ b/mxlib/client.go @@ -0,0 +1,254 @@ +package mxlib + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" + + log "github.com/sirupsen/logrus" +) + +type Client struct { + Server string + Token string + httpClient *http.Client +} + +func NewClient(server string, token string) *Client { + tr := &http.Transport{ + MaxIdleConns: 10, + IdleConnTimeout: 30 * time.Second, + DisableCompression: true, + } + return &Client{ + Server: server, + Token: token, + httpClient: &http.Client{Transport: tr}, + } +} + +func (mx *Client) GetApiCall(endpoint string, response interface{}) error { + log.Debugf("Matrix GET request: %s\n", endpoint) + + req, err := http.NewRequest("GET", mx.Server+endpoint, nil) + if err != nil { + return err + } + + return mx.DoAndParse(req, response) +} + +func (mx *Client) PutApiCall(endpoint string, data interface{}, response interface{}) error { + body, err := json.Marshal(data) + if err != nil { + return err + } + + log.Debugf("Matrix PUT request: %s %s\n", endpoint, string(body)) + + req, err := http.NewRequest("PUT", mx.Server+endpoint, bytes.NewBuffer(body)) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/json") + + return mx.DoAndParse(req, response) +} + +func (mx *Client) PostApiCall(endpoint string, data interface{}, response interface{}) error { + body, err := json.Marshal(data) + if err != nil { + return err + } + + log.Debugf("Matrix POST request: %s %s\n", endpoint, string(body)) + + req, err := http.NewRequest("POST", mx.Server+endpoint, bytes.NewBuffer(body)) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/json") + + return mx.DoAndParse(req, response) +} + +func (mx *Client) DoAndParse(req *http.Request, response interface{}) error { + req.Header.Add("Authorization", "Bearer "+mx.Token) + + resp, err := mx.httpClient.Do(req) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + var e MxError + err = json.NewDecoder(resp.Body).Decode(&e) + if err != nil { + return err + } + log.Debugf("Response (%d): %#v\n", resp.StatusCode, e) + return &e + } + + err = json.NewDecoder(resp.Body).Decode(response) + if err != nil { + return err + } + + log.Debugf("Response: %#v\n", response) + return nil +} + +// ---- + +func (mx *Client) RegisterUser(username string) error { + req := RegisterRequest{ + Username: username, + } + var rep RegisterResponse + return mx.PostApiCall("/_matrix/client/r0/register?kind=user", &req, &rep) +} + +func (mx *Client) ProfileDisplayname(userid string, displayname string) error { + req := ProfileDisplaynameRequest{ + Displayname: displayname, + } + var rep struct{} + err := mx.PutApiCall(fmt.Sprintf("/_matrix/client/r0/profile/%s/displayname?user_id=%s", + url.QueryEscape(userid), url.QueryEscape(userid)), + &req, &rep) + return err +} + +func (mx *Client) DirectoryRoom(alias string) (string, error) { + var rep DirectoryRoomResponse + err := mx.GetApiCall("/_matrix/client/r0/directory/room/"+url.QueryEscape(alias), &rep) + if err != nil { + return "", err + } + return rep.RoomId, nil +} + +func (mx *Client) CreateRoom(name string, alias string, invite []string) (string, error) { + rq := CreateRoomRequest{ + Preset: "private_chat", + RoomAliasName: alias, + Name: name, + Topic: "", + Invite: invite, + CreationContent: map[string]interface{}{ + "m.federate": false, + }, + PowerLevels: map[string]interface{}{ + "invite": 100, + "events": map[string]interface{}{ + "m.room.topic": 0, + "m.room.avatar": 0, + }, + }, + } + var rep CreateRoomResponse + err := mx.PostApiCall("/_matrix/client/r0/createRoom", &rq, &rep) + if err != nil { + return "", err + } + return rep.RoomId, nil +} + +func (mx *Client) CreateDirectRoomAs(invite []string, as_user string) (string, error) { + rq := CreateDirectRoomRequest{ + Preset: "private_chat", + Topic: "", + Invite: invite, + CreationContent: map[string]interface{}{ + "m.federate": false, + }, + PowerLevels: map[string]interface{}{ + "invite": 100, + }, + IsDirect: true, + } + var rep CreateRoomResponse + err := mx.PostApiCall("/_matrix/client/r0/createRoom?user_id="+url.QueryEscape(as_user), &rq, &rep) + if err != nil { + return "", err + } + return rep.RoomId, nil +} + +func (mx *Client) RoomInvite(room string, user string) error { + rq := RoomInviteRequest{ + UserId: user, + } + var rep struct{} + err := mx.PostApiCall("/_matrix/client/r0/rooms/"+url.QueryEscape(room)+"/invite", &rq, &rep) + return err +} + +func (mx *Client) RoomKick(room string, user string, reason string) error { + rq := RoomKickRequest{ + UserId: user, + Reason: reason, + } + var rep struct{} + err := mx.PostApiCall("/_matrix/client/r0/rooms/"+url.QueryEscape(room)+"/kick", &rq, &rep) + return err +} + +func (mx *Client) RoomJoinAs(room string, user string) error { + rq := struct{}{} + var rep RoomJoinResponse + err := mx.PostApiCall("/_matrix/client/r0/rooms/"+url.QueryEscape(room)+"/join?user_id="+url.QueryEscape(user), &rq, &rep) + return err +} + +func (mx *Client) RoomLeaveAs(room string, user string) error { + rq := struct{}{} + var rep struct{} + err := mx.PostApiCall("/_matrix/client/r0/rooms/"+url.QueryEscape(room)+"/leave?user_id="+url.QueryEscape(user), &rq, &rep) + return err +} + +func (mx *Client) SendAs(room string, event_type string, content map[string]interface{}, user string) error { + txn_id := time.Now().UnixNano() + var rep RoomSendResponse + err := mx.PutApiCall(fmt.Sprintf( + "/_matrix/client/r0/rooms/%s/send/%s/%d?user_id=%s", + url.QueryEscape(room), event_type, txn_id, url.QueryEscape(user)), + &content, &rep) + return err +} + +func (mx *Client) SendMessageAs(room string, typ string, body string, user string) error { + content := map[string]interface{}{ + "msgtype": typ, + "body": body, + } + return mx.SendAs(room, "m.room.message", content, user) +} + +func (mx *Client) PutStateAs(room string, event_type string, key string, content map[string]interface{}, as_user string) error { + var rep RoomSendResponse + err := mx.PutApiCall(fmt.Sprintf( + "/_matrix/client/r0/rooms/%s/state/%s/%s?user_id=%s", + url.QueryEscape(room), event_type, key, url.QueryEscape(as_user)), + &content, &rep) + return err +} + +func (mx *Client) RoomNameAs(room string, name string, as_user string) error { + content := map[string]interface{}{ + "name": name, + } + return mx.PutStateAs(room, "m.room.name", "", content, as_user) +} + +func (mx *Client) RoomTopicAs(room string, topic string, as_user string) error { + content := map[string]interface{}{ + "topic": topic, + } + return mx.PutStateAs(room, "m.room.topic", "", content, as_user) +}