From 372492da39e89940f9e5591606965b92f4f01acd Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 2 Dec 2024 20:58:04 +0200 Subject: [PATCH] twittermeow: parse response errors --- pkg/connector/client.go | 17 ++++++--- pkg/twittermeow/client.go | 2 +- pkg/twittermeow/errors.go | 70 ++++++++++++++++++++++++++++++++---- pkg/twittermeow/http.go | 6 +++- pkg/twittermeow/messaging.go | 7 +--- 5 files changed, 83 insertions(+), 19 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index ca9da81..0a25386 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -117,10 +117,19 @@ func (tc *TwitterClient) Connect(ctx context.Context) { _, currentUser, err := tc.client.LoadMessagesPage() if err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to load messages page") - tc.userLogin.BridgeState.Send(status.BridgeState{ - StateEvent: status.StateUnknownError, - Error: "twitter-load-error", - }) + if twittermeow.IsAuthError(err) { + tc.userLogin.BridgeState.Send(status.BridgeState{ + StateEvent: status.StateBadCredentials, + Error: "twitter-invalid-credentials", + Message: err.Error(), + }) + } else { + tc.userLogin.BridgeState.Send(status.BridgeState{ + StateEvent: status.StateUnknownError, + Error: "twitter-load-error", + Message: err.Error(), + }) + } return } diff --git a/pkg/twittermeow/client.go b/pkg/twittermeow/client.go index 60220b2..f6c1a7e 100644 --- a/pkg/twittermeow/client.go +++ b/pkg/twittermeow/client.go @@ -89,7 +89,7 @@ func (c *Client) GetCookieString() string { func (c *Client) Connect() error { if c.eventHandler == nil { - return ErrConnectPleaseSetEventHandler + return ErrConnectSetEventHandler } if !c.isAuthenticated() { diff --git a/pkg/twittermeow/errors.go b/pkg/twittermeow/errors.go index af2309f..8de6958 100644 --- a/pkg/twittermeow/errors.go +++ b/pkg/twittermeow/errors.go @@ -1,16 +1,72 @@ package twittermeow -import "errors" +import ( + "errors" + "fmt" + "strings" +) var ( - // Connection errors - ErrConnectPleaseSetEventHandler = errors.New("please set event handler in client before connecting") - ErrNotAuthenticatedYet = errors.New("client has not been authenticated yet") + ErrConnectSetEventHandler = errors.New("event handler must be set before connecting") + ErrNotAuthenticatedYet = errors.New("client has not been authenticated yet") - // Polling errors ErrAlreadyPollingUpdates = errors.New("client is already polling for user updates") ErrNotPollingUpdates = errors.New("client is not polling for user updates") +) + +type TwitterError struct { + Message string `json:"message"` + Code int `json:"code"` +} - // Api errors - ErrFailedMarkConversationRead = errors.New("failed to mark conversation as read") +var ( + ErrCouldNotAuthenticate error = TwitterError{Code: 32} + ErrUserSuspended error = TwitterError{Code: 63} + ErrAccountSuspended error = TwitterError{Code: 63} + ErrAccountTemporarilyLocked error = TwitterError{Code: 326} ) + +func IsAuthError(err error) bool { + return errors.Is(err, ErrCouldNotAuthenticate) || + errors.Is(err, ErrUserSuspended) || + errors.Is(err, ErrAccountSuspended) || + errors.Is(err, ErrAccountTemporarilyLocked) +} + +func (te TwitterError) Is(other error) bool { + var ote *TwitterError + if errors.As(other, &ote) { + return te.Code == ote.Code || te.Message == ote.Message + } + return false +} + +func (te TwitterError) Error() string { + return fmt.Sprintf("%d: %s", te.Code, te.Message) +} + +type TwitterErrors struct { + Errors []TwitterError `json:"errors"` +} + +func (te *TwitterErrors) Error() string { + if te == nil || len(te.Errors) == 0 { + return "no errors" + } else if len(te.Errors) == 1 { + return te.Errors[0].Error() + } else { + errs := make([]string, len(te.Errors)) + for i, e := range te.Errors { + errs[i] = e.Error() + } + return strings.Join(errs, ", ") + } +} + +func (te *TwitterErrors) Unwrap() []error { + errs := make([]error, len(te.Errors)) + for i, e := range te.Errors { + errs[i] = e + } + return errs +} diff --git a/pkg/twittermeow/http.go b/pkg/twittermeow/http.go index 98da3c8..edcd77e 100644 --- a/pkg/twittermeow/http.go +++ b/pkg/twittermeow/http.go @@ -2,6 +2,7 @@ package twittermeow import ( "bytes" + "encoding/json" "errors" "fmt" "io" @@ -108,7 +109,10 @@ func (c *Client) makeRequestDirect(url string, method string, headers http.Heade return nil, nil, fmt.Errorf("%w: %w", ErrResponseReadFailed, err) } if response.StatusCode >= 400 { - if len(responseBody) < 512 { + var respErr TwitterErrors + if json.Unmarshal(responseBody, &respErr) == nil { + return response, responseBody, fmt.Errorf("HTTP %d: %w", response.StatusCode, &respErr) + } else if len(responseBody) < 512 { return response, responseBody, fmt.Errorf("HTTP %d: %s", response.StatusCode, responseBody) } return response, responseBody, fmt.Errorf("HTTP %d (%d bytes of data)", response.StatusCode, len(responseBody)) diff --git a/pkg/twittermeow/messaging.go b/pkg/twittermeow/messaging.go index adae4ae..4fd2232 100644 --- a/pkg/twittermeow/messaging.go +++ b/pkg/twittermeow/messaging.go @@ -69,16 +69,11 @@ func (c *Client) MarkConversationRead(params *payload.MarkConversationReadQuery) Body: encodedQueryBody, ContentType: types.FORM, } - resp, respBody, err := c.makeAPIRequest(apiRequestOpts) + _, _, err = c.makeAPIRequest(apiRequestOpts) if err != nil { return err } - if resp.StatusCode > 204 { - c.Logger.Warn().Any("response_body", string(respBody)).Any("status_code", resp.StatusCode).Any("params", params).Msg("Failed to mark conversation as read") - return ErrFailedMarkConversationRead - } - return nil }