From 03705261195544b6219ddf12c56b81f0e1fc3654 Mon Sep 17 00:00:00 2001 From: PL Pery Date: Thu, 13 Jan 2022 16:59:12 +0100 Subject: [PATCH] mapping: Interactions (#23) * mapping: Interactions This commit should contain all the https://discord.com/developers/docs/interactions/receiving-and-responding mapped * fix tests * use API v9 * remove unecessary path.Clean --- commands.go | 2 +- interactions-api.go | 154 ++++++++++++++++++++++++++++++++++++ internal/rest/client.go | 10 ++- internal/rest/reqBuilder.go | 6 +- owmock/http.go | 13 ++- responder.go | 3 +- sample-component_test.go | 9 +-- users.go | 4 +- 8 files changed, 180 insertions(+), 21 deletions(-) create mode 100644 interactions-api.go diff --git a/commands.go b/commands.go index 8ad5a5a..11ab599 100644 --- a/commands.go +++ b/commands.go @@ -84,7 +84,7 @@ func (m *Mux) GetCommands(options ...func(*CommandsOpt)) ([]Command, error) { r.Append("commands") var commands []Command - _, err := rest.DoJson(m.Client, r.Get(m.authorize, rest.JSON), &commands) + _, err := rest.DoJSON(m.Client, r.Get(m.authorize, rest.JSON), &commands) if err != nil { return nil, err } diff --git a/interactions-api.go b/interactions-api.go new file mode 100644 index 0000000..04ebbd6 --- /dev/null +++ b/interactions-api.go @@ -0,0 +1,154 @@ +package corde + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "mime/multipart" + + "github.com/Karitham/corde/internal/rest" +) + +// returns the body and its content-type +func toBody(i *InteractionRespData) (*bytes.Buffer, string) { + body := new(bytes.Buffer) + contentType := "application/json" + + payloadJSON := &bytes.Buffer{} + err := json.NewEncoder(payloadJSON).Encode(i) + if err != nil { + return nil, "" + } + + if len(i.Attachments) < 1 { + payloadJSON.WriteTo(body) + return body, contentType + } + + mw := multipart.NewWriter(body) + defer mw.Close() + + contentType = mw.FormDataContentType() + mw.WriteField("payload_json", payloadJSON.String()) + + for i, f := range i.Attachments { + if f.ID == 0 { + f.ID = Snowflake(i) + } + + ff, CFerr := mw.CreateFormFile(fmt.Sprintf("files[%d]", i), f.Filename) + if CFerr != nil { + return body, contentType + } + + if _, CopyErr := io.Copy(ff, f.Body); CopyErr != nil { + return body, contentType + } + } + return body, contentType +} + +// GetOriginalInteraction returns the original response to an Interaction +// +// https://discord.com/developers/docs/interactions/receiving-and-responding#get-original-interaction-response +func (m *Mux) GetOriginalInteraction(token string) (*InteractionRespData, error) { + data := &InteractionRespData{} + _, err := rest.DoJSON(m.Client, rest.Req("/webhooks", m.AppID, token, "messages/@original").Get(m.authorize), data) + if err != nil { + return nil, err + } + + return data, nil +} + +// EditOriginalInteraction to edit your initial response to an Interaction +// +// https://discord.com/developers/docs/interactions/receiving-and-responding#edit-original-interaction-response +func (m *Mux) EditOriginalInteraction(token string, data InteractionResponder) error { + body, contentType := toBody(data.InteractionRespData()) + + _, err := m.Client.Do( + rest.Req("/webhooks", m.AppID, token, "messages/@original"). + AnyBody(body).Patch(m.authorize, rest.ContentType(contentType)), + ) + if err != nil { + return err + } + return nil +} + +// DeleteOriginalInteraction to delete your initial response to an Interaction +// +// https://discord.com/developers/docs/interactions/receiving-and-responding#edit-original-interaction-response +func (m *Mux) DeleteOriginalInteraction(token string) error { + _, err := m.Client.Do( + rest.Req("/webhooks", m.AppID, token, "messages/@original"). + Delete(m.authorize), + ) + if err != nil { + return err + } + + return nil +} + +// FollowUpInteraction follows up a response to an Interaction +// +// https://discord.com/developers/docs/interactions/receiving-and-responding#followup-messages +func (m *Mux) FollowUpInteraction(token string, data InteractionResponder) error { + body, contentType := toBody(data.InteractionRespData()) + + _, err := m.Client.Do( + rest.Req("/webhooks", m.AppID, token). + AnyBody(body).Post(m.authorize, rest.ContentType(contentType)), + ) + if err != nil { + return err + } + return nil +} + +// GetFollowUpInteraction returns the response to a FollowUpInteraction +// +// https://discord.com/developers/docs/interactions/receiving-and-responding#get-followup-message +func (m *Mux) GetFollowUpInteraction(token string, messageID Snowflake) (*InteractionRespData, error) { + data := &InteractionRespData{} + _, err := rest.DoJSON(m.Client, rest.Req("/webhooks", m.AppID, token, "messages", messageID).Get(m.authorize), data) + if err != nil { + return nil, err + } + + return data, nil +} + +// EditFollowUpInteraction to edit a response to a FollowUpInteraction +// +// https://discord.com/developers/docs/interactions/receiving-and-responding#edit-followup-message +func (m *Mux) EditFollowUpInteraction(token string, messageID Snowflake, data InteractionResponder) error { + body, contentType := toBody(data.InteractionRespData()) + + _, err := m.Client.Do( + rest.Req("/webhooks", m.AppID, token, "messages", messageID). + AnyBody(body).Patch(m.authorize, rest.ContentType(contentType)), + ) + if err != nil { + return err + } + return nil +} + +// DeleteFollowUpInteraction to delete a response to a FollowUpInteraction +// +// https://discord.com/developers/docs/interactions/receiving-and-responding#delete-followup-message +func (m *Mux) DeleteFollowUpInteraction(token string, messageID Snowflake) error { + _, err := m.Client.Do( + rest.Req("/webhooks", m.AppID, token, "messages", messageID). + Delete(m.authorize), + ) + if err != nil { + return err + } + + return nil +} diff --git a/internal/rest/client.go b/internal/rest/client.go index 082b79d..aef6325 100644 --- a/internal/rest/client.go +++ b/internal/rest/client.go @@ -5,7 +5,9 @@ import ( "net/http" ) -func DoJson(c *http.Client, r *http.Request, v any) (*http.Response, error) { +// DoJSON executes a request and decodes the response into the given interface +// It already calls `Close()` on the body +func DoJSON(c *http.Client, r *http.Request, v any) (*http.Response, error) { resp, err := c.Do(r) if err != nil { return nil, err @@ -18,3 +20,9 @@ func DoJson(c *http.Client, r *http.Request, v any) (*http.Response, error) { return resp, nil } + +func ContentType(contentType string) func(*http.Request) { + return func(r *http.Request) { + r.Header.Set("content-type", contentType) + } +} diff --git a/internal/rest/reqBuilder.go b/internal/rest/reqBuilder.go index 1d6778a..31df7f4 100644 --- a/internal/rest/reqBuilder.go +++ b/internal/rest/reqBuilder.go @@ -16,7 +16,7 @@ type Request struct { body io.Reader } -var API = "https://discord.com/api/v8" +var API = "https://discord.com/api/v9" func Req(paths ...any) *Request { r := &Request{ @@ -69,6 +69,10 @@ func (r *Request) Delete(opts ...func(*http.Request)) *http.Request { return r.new(http.MethodDelete, r.body, opts...) } +func (r *Request) Patch(opts ...func(*http.Request)) *http.Request { + return r.new(http.MethodPatch, r.body, opts...) +} + func JSON(r *http.Request) { r.Header.Set("content-type", "application/json") } diff --git a/owmock/http.go b/owmock/http.go index 3f2d367..cf5c65f 100644 --- a/owmock/http.go +++ b/owmock/http.go @@ -6,10 +6,10 @@ import ( "encoding/hex" "encoding/json" "errors" - "log" "net/http" "reflect" "strconv" + "testing" "time" "github.com/matryer/is" @@ -44,8 +44,6 @@ func req(c Doer, method string, url string, buf *bytes.Buffer, privK ed25519.Pri if err = json.NewDecoder(resp.Body).Decode(&respBody); err != nil { return nil, err } - - log.Println(string(respBody)) return respBody, nil } @@ -103,15 +101,16 @@ func (r *Requester) Post(body string) (json.RawMessage, error) { } // PostExpect posts a payload and expects a response with the given body -func (r *Requester) PostExpect(t is.T, body any, expectV any) error { +func (r *Requester) PostExpect(t *testing.T, body string, expectV any) error { is := is.New(t) - resp, err := r.PostJSON(body) + resp, err := r.Post(body) is.NoErr(err) - typ := reflect.TypeOf(expectV) + typ := reflect.TypeOf(expectV).Elem() respV := reflect.New(typ).Interface() - err = json.Unmarshal(resp, respV) + b, _ := resp.MarshalJSON() + err = json.Unmarshal(b, respV) is.NoErr(err) is.Equal(respV, expectV) diff --git a/responder.go b/responder.go index ca9bb39..520c32d 100644 --- a/responder.go +++ b/responder.go @@ -74,7 +74,8 @@ func (r *Responder) respond(i intResponse) { mw := multipart.NewWriter(r.w) defer mw.Close() - r.w.Header().Set("Content-Type", mw.FormDataContentType()) + contentType := mw.FormDataContentType() + r.w.Header().Set("content-type", contentType) mw.WriteField("payload_json", payloadJSON.String()) for i, f := range i.Data.Attachments { diff --git a/sample-component_test.go b/sample-component_test.go index abecf11..7263219 100644 --- a/sample-component_test.go +++ b/sample-component_test.go @@ -1,7 +1,6 @@ package corde_test import ( - "encoding/json" "net/http/httptest" "testing" @@ -29,14 +28,8 @@ func TestComponentInteraction(t *testing.T) { } s := httptest.NewServer(mux.Handler()) - respPost, err := owmock.NewWithClient(s.URL, s.Client()).Post(SampleComponent) + err := owmock.NewWithClient(s.URL, s.Client()).PostExpect(t, SampleComponent, expect) assert.NoErr(err) - - respV := &owmock.InteractionResponse{} - err = json.Unmarshal(respPost, respV) - assert.NoErr(err) - - assert.Equal(expect, respV) } const SampleComponent = `{ diff --git a/users.go b/users.go index 24e46a1..5d9f6f5 100644 --- a/users.go +++ b/users.go @@ -7,13 +7,13 @@ import ( // Me returns the current user func (m *Mux) Me() (User, error) { var user User - _, err := rest.DoJson(m.Client, rest.Req("/users/@me").Get(m.authorize), &user) + _, err := rest.DoJSON(m.Client, rest.Req("/users/@me").Get(m.authorize), &user) return user, err } // GetUser returns a user by id func (m *Mux) GetUser(id Snowflake) (User, error) { var user User - _, err := rest.DoJson(m.Client, rest.Req("/users/", id).Get(m.authorize), &user) + _, err := rest.DoJSON(m.Client, rest.Req("/users/", id).Get(m.authorize), &user) return user, err }