package appservice import ( "bytes" "encoding/json" "fmt" "net/http" "net/url" "time" log "github.com/sirupsen/logrus" . "git.deuxfleurs.fr/Deuxfleurs/easybridge/mxlib" "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, 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(name string, invite []string, as_user string) (string, error) { rq := CreateRoomNoAliasRequest{ Preset: "private_chat", Name: name, 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) }