From 6c242710d7b1e4ba7c7d9b76437529d1d00c7c67 Mon Sep 17 00:00:00 2001 From: hanemile Date: Fri, 10 Jul 2020 16:37:52 +0200 Subject: the functions handling almost everything I need --- join.go | 32 +++++++++++++++++ login.go | 35 ++----------------- send.go | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ structs.go | 72 +++++++++++++++++++++++++++++++++++++++ sync.go | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 315 insertions(+), 33 deletions(-) create mode 100644 join.go create mode 100644 send.go create mode 100644 structs.go create mode 100644 sync.go diff --git a/join.go b/join.go new file mode 100644 index 0000000..bc274c0 --- /dev/null +++ b/join.go @@ -0,0 +1,32 @@ +package matrix + +import ( + "fmt" + + "gopkg.in/h2non/gentleman.v2" + "gopkg.in/h2non/gentleman.v2/plugins/query" +) + +func join(authinfo Authinfo, roomIdentifier string) error { + cli := gentleman.New() + cli.URL(authinfo.HomeServer) + + req := cli.Request() + req.Path(fmt.Sprintf("/_matrix/client/r0/rooms/%s/join", roomIdentifier)) + req.Method("POST") + + req.Use(query.Set("access_token", authinfo.AccessToken)) + + res, err := req.Send() + if err != nil { + fmt.Println("ERR1") + return err + } + if !res.Ok { + fmt.Println("ERR2") + fmt.Println(res) + return err + } + + return nil +} diff --git a/login.go b/login.go index 3e35858..aa24db5 100644 --- a/login.go +++ b/login.go @@ -3,9 +3,8 @@ package matrix import ( "encoding/json" - "github.com/h2non/gentleman/plugins/body" "gopkg.in/h2non/gentleman.v2" - "gopkg.in/h2non/gentleman.v2/plugins/query" + "gopkg.in/h2non/gentleman.v2/plugins/body" ) // Login logs in to the homeserver and returns an Authinfo struct containing @@ -38,38 +37,8 @@ func Login(username, password, homeserver string) (Authinfo, error) { if err := json.Unmarshal(res.Bytes(), &authinfo); err != nil { return Authinfo{}, err } - return authinfo, nil -} -func Sync(authinfo Authinfo) { - cli := gentleman.New() - cli.URL(authinfo.HomeServer) - - req := cli.Request() - req.Path("/_matrix/client/r0/sync") - req.Method("GET") - - req.Use(query.Set("access_token", authinfo.AccessToken)) - - res, err := req.Send() - if err != nil { - return err - } - if !res.Ok { - return err - } + authinfo.HomeServer = homeserver - var authinfo Authinfo - if err := json.Unmarshal(res.Bytes(), &authinfo); err != nil { - return Authinfo{}, err - } return authinfo, nil } - -// Authinfo defines the fields returned after logging in -type Authinfo struct { - UserID string `json:"user_id"` - HomeServer string `json:"home_server"` - DeviceID string `json:"device_id"` - AccessToken string `json:"access_token"` -} diff --git a/send.go b/send.go new file mode 100644 index 0000000..3547eaa --- /dev/null +++ b/send.go @@ -0,0 +1,113 @@ +package matrix + +import ( + "bytes" + "encoding/json" + "fmt" + "time" + + "gopkg.in/h2non/gentleman.v2" + "gopkg.in/h2non/gentleman.v2/plugins/body" + "gopkg.in/h2non/gentleman.v2/plugins/headers" + "gopkg.in/h2non/gentleman.v2/plugins/query" +) + +// Send allows sending messages +func Send(authinfo Authinfo, roomID string, message string) error { + cli := gentleman.New() + cli.URL(authinfo.HomeServer) + + eventType := "m.room.message" + txID := fmt.Sprintf("%d", time.Now().UnixNano()) + + req := cli.Request() + req.Path(fmt.Sprintf("/_matrix/client/r0/rooms/%s/send/%s/%s", roomID, eventType, txID)) + req.Method("PUT") + + formattedMessage := fmt.Sprintf("
%s\n
\n", message) + + // Define the JSON payload via body plugin + data := map[string]string{ + "msgtype": "m.text", + "body": "A", // notifications + "format": "org.matrix.custom.html", + "formatted_body": formattedMessage, + } + req.Use(body.JSON(data)) + req.Use(query.Set("access_token", authinfo.AccessToken)) + + res, err := req.Send() + if err != nil { + fmt.Println("ERR1") + return err + } + if !res.Ok { + fmt.Println("ERR2") + fmt.Println(res) + return err + } + fmt.Printf("Sent %s\n", message) + return nil +} + +// Upload uploads stuff to the matrix homeserver returning the files MXC +func Upload(authinfo Authinfo, filename string, file *bytes.Buffer) (UploadResponse, error) { + cli := gentleman.New() + cli.URL(authinfo.HomeServer) + + req := cli.Request() + req.Path("/_matrix/media/r0/upload") + req.Method("POST") + + req.Use(headers.Set("Content-Type", "image/png")) + req.Use(headers.Set("filename", filename)) + + req.Use(body.Reader(file)) + req.Use(query.Set("access_token", authinfo.AccessToken)) + + res, err := req.Send() + if err != nil { + fmt.Println("ERR1") + return UploadResponse{}, err + } + if !res.Ok { + fmt.Println("ERR2") + fmt.Println(res) + return UploadResponse{}, err + } + + var uploadResponse UploadResponse + if err := json.Unmarshal(res.Bytes(), &uploadResponse); err != nil { + return UploadResponse{}, err + } + return uploadResponse, nil +} + +// SendImage sends the image with the given mxc ID to the room (currently +// hardcoded some lines below) +func SendImage(authinfo Authinfo, roomID string, image map[string]interface{}) error { + cli := gentleman.New() + cli.URL(authinfo.HomeServer) + + eventType := "m.room.message" + txID := fmt.Sprintf("%d", time.Now().UnixNano()) + + req := cli.Request() + req.Path(fmt.Sprintf("/_matrix/client/r0/rooms/%s/send/%s/%s", roomID, eventType, txID)) + req.Method("PUT") + + req.Use(body.JSON(image)) + req.Use(query.Set("access_token", authinfo.AccessToken)) + + res, err := req.Send() + if err != nil { + fmt.Println("ERR1") + return err + } + if !res.Ok { + fmt.Println("ERR2") + fmt.Println(res) + return err + } + return nil +} diff --git a/structs.go b/structs.go new file mode 100644 index 0000000..d07b103 --- /dev/null +++ b/structs.go @@ -0,0 +1,72 @@ +package matrix + +// Authinfo defines the fields returned after logging in +type Authinfo struct { + UserID string `json:"user_id"` + HomeServer string `json:"home_server"` + DeviceID string `json:"device_id"` + AccessToken string `json:"access_token"` +} + +// RespSync defines the response from the sync +type RespSync struct { + NextBatch string `json:"next_batch"` + AccountData struct { + Events []Event `json:"events"` + } `json:"account_data"` + Presence struct { + Events []Event `json:"events"` + } `json:"presence"` + Rooms struct { + Leave map[string]struct { + State struct { + Events []Event `json:"events"` + } `json:"state"` + Timeline struct { + Events []Event `json:"events"` + Limited bool `json:"limited"` + PrevBatch string `json:"prev_batch"` + } `json:"timeline"` + } `json:"leave"` + Join map[string]struct { + State struct { + Events []Event `json:"events"` + } `json:"state"` + Timeline struct { + Events []Event `json:"events"` + Limited bool `json:"limited"` + PrevBatch string `json:"prev_batch"` + } `json:"timeline"` + } `json:"join"` + Invite map[string]struct { + State struct { + Events []Event + } `json:"invite_state"` + } `json:"invite"` + } `json:"rooms"` +} + +// Event defines an 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 string `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) + Redacts string `json:"redacts,omitempty"` // The event ID that was redacted if a m.room.redaction event + Unsigned map[string]interface{} `json:"unsigned"` // The unsigned portions of the event, such as age and prev_content + Content map[string]interface{} `json:"content"` // The JSON content of the event. + PrevContent map[string]interface{} `json:"prev_content,omitempty"` // The JSON prev_content of the event. +} + +// PackagedEvent bundles an event with more information regarding it, such as the roomname in which the event was produced. +type PackagedEvent struct { + RoomName string + Event Event +} + +// UploadResponse is the responce recieved from the upload +type UploadResponse struct { + ContentURI string `json:"content_uri,omitempty"` +} diff --git a/sync.go b/sync.go new file mode 100644 index 0000000..b1f5544 --- /dev/null +++ b/sync.go @@ -0,0 +1,96 @@ +package matrix + +import ( + "encoding/json" + "fmt" + + "gopkg.in/h2non/gentleman.v2" + "gopkg.in/h2non/gentleman.v2/plugins/query" +) + +// Sync syncs +func Sync(authinfo Authinfo) (RespSync, error) { + cli := gentleman.New() + cli.URL(authinfo.HomeServer) + + req := cli.Request() + req.Path("/_matrix/client/r0/sync") + req.Method("GET") + + req.Use(query.Set("access_token", authinfo.AccessToken)) + + res, err := req.Send() + if err != nil { + fmt.Println("ERR1") + return RespSync{}, err + } + if !res.Ok { + fmt.Println("ERR2") + fmt.Println(res) + return RespSync{}, err + } + + var syncReponse RespSync + if err := json.Unmarshal(res.Bytes(), &syncReponse); err != nil { + return RespSync{}, err + } + + return syncReponse, nil +} + +// SyncPartial syncs the state using sync token obtained in a previous request +// as a timestamp +func SyncPartial(authinfo Authinfo, nextBatch string, eventsChannel chan PackagedEvent) (RespSync, error) { + cli := gentleman.New() + cli.URL(authinfo.HomeServer) + + req := cli.Request() + req.Path("/_matrix/client/r0/sync") + req.Method("GET") + + req.Use(query.Set("access_token", authinfo.AccessToken)) + req.Use(query.Set("since", nextBatch)) + req.Use(query.Set("timeout", "1")) + + res, err := req.Send() + if err != nil { + fmt.Println("ERR1") + return RespSync{}, err + } + if !res.Ok { + fmt.Println("ERR2") + fmt.Println(res) + return RespSync{}, err + } + + // unmarshal the response + var syncReponse RespSync + if err := json.Unmarshal(res.Bytes(), &syncReponse); err != nil { + return RespSync{}, err + } + + // accept all room invites + for room := range syncReponse.Rooms.Invite { + err := join(authinfo, room) + if err != nil { + return RespSync{}, fmt.Errorf("could not join room: %s", err) + } + } + + // iterate over all new events, insert all new events not from the bot itself into the eventsChannel + for roomname, room := range syncReponse.Rooms.Join { + for _, event := range room.Timeline.Events { + + packagedEvent := PackagedEvent{ + RoomName: roomname, + Event: event, + } + + // if the event recieved is not from ourself, insert the event into the eventsChannel + if event.Sender != authinfo.UserID { + eventsChannel <- packagedEvent + } + } + } + return syncReponse, nil +} -- cgit 1.4.1