From 1d4cbd9a669d9231a3daf123b3c6e25a8f25bb7b Mon Sep 17 00:00:00 2001 From: Knut Ahlers Date: Thu, 28 Mar 2024 00:09:28 +0100 Subject: [PATCH] [eventsub] Fix: Properly handle 409 error by fetching the existing subscription instead of failing to access the expected response which is not there in case of a collision Signed-off-by: Knut Ahlers --- pkg/twitch/eventsub.go | 60 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/pkg/twitch/eventsub.go b/pkg/twitch/eventsub.go index 9e614c8..4715304 100644 --- a/pkg/twitch/eventsub.go +++ b/pkg/twitch/eventsub.go @@ -6,10 +6,12 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "time" "github.com/mitchellh/hashstructure/v2" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // Collection of known EventSub event-types @@ -277,8 +279,10 @@ func (c *Client) createEventSubSubscriptionWebsocket(ctx context.Context, sub ev func (c *Client) createEventSubSubscription(ctx context.Context, auth AuthType, sub eventSubSubscription) (*eventSubSubscription, error) { var ( - buf = new(bytes.Buffer) - resp struct { + buf = new(bytes.Buffer) + err error + mustFetchSubsctiption bool + resp struct { Total int64 `json:"total"` Data []eventSubSubscription `json:"data"` Pagination struct { @@ -287,11 +291,16 @@ func (c *Client) createEventSubSubscription(ctx context.Context, auth AuthType, } ) - if err := json.NewEncoder(buf).Encode(sub); err != nil { + conHash, err := sub.Condition.Hash() + if err != nil { + return nil, fmt.Errorf("hashing input condition: %w", err) + } + + if err = json.NewEncoder(buf).Encode(sub); err != nil { return nil, errors.Wrap(err, "assemble subscribe payload") } - if err := c.Request(ctx, ClientRequestOpts{ + if err = c.Request(ctx, ClientRequestOpts{ AuthType: auth, Body: buf, Method: http.MethodPost, @@ -301,6 +310,7 @@ func (c *Client) createEventSubSubscription(ctx context.Context, auth AuthType, ValidateFunc: func(opts ClientRequestOpts, resp *http.Response) error { if resp.StatusCode == http.StatusConflict { // This is fine: We needed that subscription, it exists + mustFetchSubsctiption = true return nil } @@ -308,8 +318,46 @@ func (c *Client) createEventSubSubscription(ctx context.Context, auth AuthType, return ValidateStatus(opts, resp) }, }); err != nil { - return nil, errors.Wrap(err, "executing request") + return nil, fmt.Errorf("creating subscription: %w", err) } - return &resp.Data[0], nil + if mustFetchSubsctiption { + params := make(url.Values) + params.Set("status", "enabled") + params.Set("type", sub.Type) + + if err = c.Request(ctx, ClientRequestOpts{ + AuthType: auth, + Method: http.MethodGet, + OKStatus: http.StatusOK, + Out: &resp, + URL: fmt.Sprintf("https://api.twitch.tv/helix/eventsub/subscriptions?%s", params.Encode()), + }); err != nil { + return nil, fmt.Errorf("fetching subscription: %w", err) + } + } + + for i := range resp.Data { + s := resp.Data[i] + + if s.Type != sub.Type || s.Version != sub.Version { + // Not the subscription we're searching for + continue + } + + sConHash, err := s.Condition.Hash() + if err != nil { + logrus.WithError(err).Error("hashing eventsub subscription condition") + continue + } + + if sConHash != conHash { + // Different conditions + continue + } + + return &s, nil + } + + return nil, fmt.Errorf("no subscription matching input found") }