diff --git a/go.mod b/go.mod index 0e8c126..016190c 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,13 @@ module go.arsenm.dev/go-lemmy go 1.19 -require github.com/google/go-querystring v1.1.0 +require ( + github.com/google/go-querystring v1.1.0 + github.com/mitchellh/mapstructure v1.5.0 + github.com/recws-org/recws v1.4.0 +) + +require ( + github.com/gorilla/websocket v1.4.2 // indirect + github.com/jpillora/backoff v1.0.0 // indirect +) diff --git a/go.sum b/go.sum index f99081b..c4801d4 100644 --- a/go.sum +++ b/go.sum @@ -2,4 +2,12 @@ github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/recws-org/recws v1.4.0 h1:y9LLddtAicjejikNZXiaY9DQjIwcAQ82acd1XU6n0lU= +github.com/recws-org/recws v1.4.0/go.mod h1:7+NQkTmBdU98VSzkzq9/P7+X0xExioUVBx9OeRKQIkk= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/types/types.go b/types/types.go index 2a86577..126e549 100644 --- a/types/types.go +++ b/types/types.go @@ -25,3 +25,8 @@ type LemmyError struct { func (le LemmyError) Error() string { return fmt.Sprintf("%d %s: %s", le.Code, http.StatusText(le.Code), le.ErrStr) } + +type LemmyWebSocketMsg struct { + Op UserOperation `json:"op"` + Data any `json:"data"` +} diff --git a/websocket.go b/websocket.go new file mode 100644 index 0000000..d45660b --- /dev/null +++ b/websocket.go @@ -0,0 +1,76 @@ +package lemmy + +import ( + "net/url" + "time" + + "github.com/mitchellh/mapstructure" + "github.com/recws-org/recws" + "go.arsenm.dev/go-lemmy/types" +) + +type WSClient struct { + conn *recws.RecConn + respCh chan types.LemmyWebSocketMsg + errCh chan error +} + +func NewWebSocket(baseURL string) (*WSClient, error) { + ws := &recws.RecConn{ + KeepAliveTimeout: 10 * time.Second, + } + + u, err := url.Parse(baseURL) + if err != nil { + return nil, err + } + u = u.JoinPath("/api/v3") + + ws.Dial(u.String(), nil) + + out := &WSClient{ + conn: ws, + respCh: make(chan types.LemmyWebSocketMsg, 10), + errCh: make(chan error, 10), + } + + go func() { + for { + var msg types.LemmyWebSocketMsg + err = ws.ReadJSON(&msg) + if err != nil { + out.errCh <- err + continue + } + out.respCh <- msg + } + }() + + return out, nil +} + +func (c *WSClient) Request(op types.UserOperation, data any) error { + return c.conn.WriteJSON(types.LemmyWebSocketMsg{ + Op: op, + Data: data, + }) +} + +func (c *WSClient) Responses() <-chan types.LemmyWebSocketMsg { + return c.respCh +} + +func (c *WSClient) Errors() <-chan error { + return c.errCh +} + +func DecodeResponse(data, out any) error { + dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + TagName: "json", + Result: out, + }) + if err != nil { + return err + } + return dec.Decode(data) +}