From 3769716a29f8c58407d3562d65d3445256e13bcb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:41:26 +0000 Subject: [PATCH] build(deps): bump github.com/nicklaw5/helix/v2 in /src Bumps [github.com/nicklaw5/helix/v2](https://github.com/nicklaw5/helix) from 2.32.0 to 2.34.0. - [Release notes](https://github.com/nicklaw5/helix/releases) - [Commits](https://github.com/nicklaw5/helix/compare/v2.32.0...v2.34.0) --- updated-dependencies: - dependency-name: github.com/nicklaw5/helix/v2 dependency-version: 2.34.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- src/go.mod | 2 +- src/go.sum | 4 +- .../nicklaw5/helix/v2/SUPPORTED_ENDPOINTS.md | 2 +- .../github.com/nicklaw5/helix/v2/channels.go | 2 +- .../github.com/nicklaw5/helix/v2/clips.go | 8 +- .../github.com/nicklaw5/helix/v2/helix.go | 129 ++++++++++++++++-- .../github.com/nicklaw5/helix/v2/schedule.go | 12 +- .../github.com/nicklaw5/helix/v2/teams.go | 49 +++++++ .../github.com/nicklaw5/helix/v2/videos.go | 2 +- src/vendor/modules.txt | 2 +- 10 files changed, 185 insertions(+), 27 deletions(-) create mode 100644 src/vendor/github.com/nicklaw5/helix/v2/teams.go diff --git a/src/go.mod b/src/go.mod index fcaaa5d..c3cfef6 100644 --- a/src/go.mod +++ b/src/go.mod @@ -4,7 +4,7 @@ go 1.26 require ( github.com/go-git/go-git/v5 v5.16.5 - github.com/nicklaw5/helix/v2 v2.32.0 + github.com/nicklaw5/helix/v2 v2.34.0 github.com/nikoksr/notify v1.5.0 github.com/sirupsen/logrus v1.9.4 ) diff --git a/src/go.sum b/src/go.sum index 2a0d4e9..a18fbcb 100644 --- a/src/go.sum +++ b/src/go.sum @@ -54,8 +54,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/nicklaw5/helix/v2 v2.32.0 h1:ZRPt+wRUMQqpny6yZKVY9rUGNwv+ZmIh75fSiopMXuY= -github.com/nicklaw5/helix/v2 v2.32.0/go.mod h1:KaXa2mb2kBzsDana9RbXevTgnfU95DMoSORWo2hqlWA= +github.com/nicklaw5/helix/v2 v2.34.0 h1:mdio9Wm+TQIjuJ199myvcKEcAmHiqigHsZlG1fDaPIE= +github.com/nicklaw5/helix/v2 v2.34.0/go.mod h1:KaXa2mb2kBzsDana9RbXevTgnfU95DMoSORWo2hqlWA= github.com/nikoksr/notify v1.5.0 h1:mzkCw8eb0P+qHwgmGQyPPGqz4GH+07FJDr44Bs16T9k= github.com/nikoksr/notify v1.5.0/go.mod h1:CEV9Bw9Y59K5oj7d8h83Xl32ATeL43ZEg9qTQsfwcCc= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= diff --git a/src/vendor/github.com/nicklaw5/helix/v2/SUPPORTED_ENDPOINTS.md b/src/vendor/github.com/nicklaw5/helix/v2/SUPPORTED_ENDPOINTS.md index cdaf513..8f20650 100644 --- a/src/vendor/github.com/nicklaw5/helix/v2/SUPPORTED_ENDPOINTS.md +++ b/src/vendor/github.com/nicklaw5/helix/v2/SUPPORTED_ENDPOINTS.md @@ -111,7 +111,7 @@ - [x] Get Broadcaster Subscriptions - [x] Check User Subscription - [ ] Get Channel Teams -- [ ] Get Teams +- [x] Get Teams - [x] Get Users - [x] Update User - [x] Get Users Follows diff --git a/src/vendor/github.com/nicklaw5/helix/v2/channels.go b/src/vendor/github.com/nicklaw5/helix/v2/channels.go index 98640dd..42c2081 100644 --- a/src/vendor/github.com/nicklaw5/helix/v2/channels.go +++ b/src/vendor/github.com/nicklaw5/helix/v2/channels.go @@ -88,7 +88,7 @@ type ManyFollowedChannels struct { type FollowedChannel struct { BroadcasterID string `json:"broadcaster_id"` BroadcasterName string `json:"broadcaster_name"` - BroadcaserLogin string `json:"broadcaster_login"` + BroadcasterLogin string `json:"broadcaster_login"` Followed Time `json:"followed_at"` } diff --git a/src/vendor/github.com/nicklaw5/helix/v2/clips.go b/src/vendor/github.com/nicklaw5/helix/v2/clips.go index 55ce7b8..c148dc8 100644 --- a/src/vendor/github.com/nicklaw5/helix/v2/clips.go +++ b/src/vendor/github.com/nicklaw5/helix/v2/clips.go @@ -85,10 +85,12 @@ func (ccr *CreateClipResponse) GetClipsCreationRateLimitRemaining() int { } type CreateClipParams struct { + // BroadcasterID The ID of the broadcaster whose stream you want to create a clip from. BroadcasterID string `query:"broadcaster_id"` - - // Optional - HasDelay bool `query:"has_delay,false"` + // Optional. The title of the clip. + Title string `query:"title"` + // Optional. The length of the clip in seconds. Possible values range from 5 to 60 inclusively with a precision of 0.1. The default is 30. + Duration float32 `query:"duration"` } // CreateClip creates a clip programmatically. This returns both an ID and diff --git a/src/vendor/github.com/nicklaw5/helix/v2/helix.go b/src/vendor/github.com/nicklaw5/helix/v2/helix.go index 78de4ac..d0e04ce 100644 --- a/src/vendor/github.com/nicklaw5/helix/v2/helix.go +++ b/src/vendor/github.com/nicklaw5/helix/v2/helix.go @@ -9,6 +9,7 @@ import ( "io" "log" "net/http" + "os" "reflect" "strconv" "strings" @@ -28,6 +29,12 @@ type HTTPClient interface { Do(req *http.Request) (*http.Response, error) } +// Logger is the interface used by the helix client for debug logging. +// The stdlib *log.Logger satisfies this interface. +type Logger interface { + Printf(format string, v ...interface{}) +} + type Client struct { mu sync.RWMutex ctx context.Context @@ -35,6 +42,7 @@ type Client struct { lastResponse *Response callbacks struct { onUserAccessTokenRefreshed func(newAccessToken, newRefreshToken string) + onAppAccessTokenRefreshed func(newAccessToken string) } } @@ -42,6 +50,7 @@ type Options struct { ClientID string ClientSecret string AppAccessToken string + AppAccessScopes []string DeviceAccessToken string UserAccessToken string RefreshToken string @@ -51,6 +60,14 @@ type Options struct { RateLimitFunc RateLimitFunc APIBaseURL string ExtensionOpts ExtensionOptions + // DebugMode enables debug logging of outgoing requests and incoming responses. + // WARNING: debug logs may contain sensitive data such as tokens and API responses. + // Only enable in non-production environments. + DebugMode bool + // Logger is the logger used when DebugMode is true. If nil, a default logger + // writing to os.Stderr is used. Any type implementing Printf(string, ...interface{}) + // is accepted (e.g. *log.Logger). + Logger Logger } type ExtensionOptions struct { @@ -67,6 +84,25 @@ type DateRange struct { type RateLimitFunc func(*Response) error +// DefaultRateLimitFunc is a default rate limit function that sleeps until the +// rate limit reset time if the rate limit has been reached (i.e. no remaining +// requests). It can be used as the RateLimitFunc in Options. +func DefaultRateLimitFunc(lastResponse *Response) error { + if lastResponse.GetRateLimitRemaining() > 0 { + return nil + } + + reset64 := int64(lastResponse.GetRateLimitReset()) + currentTime := time.Now().Unix() + + if currentTime < reset64 { + timeDiff := time.Duration(reset64-currentTime) * time.Second + time.Sleep(timeDiff) + } + + return nil +} + type ResponseCommon struct { StatusCode int Header http.Header @@ -133,6 +169,10 @@ func NewClientWithContext(ctx context.Context, options *Options) (*Client, error options.APIBaseURL = DefaultAPIBaseURL } + if options.DebugMode && options.Logger == nil { + options.Logger = log.New(os.Stderr, "", log.LstdFlags) + } + client := &Client{ ctx: ctx, opts: options, @@ -141,6 +181,12 @@ func NewClientWithContext(ctx context.Context, options *Options) (*Client, error return client, nil } +func (c *Client) logf(format string, v ...interface{}) { + if c.opts.DebugMode && c.opts.Logger != nil { + c.opts.Logger.Printf(format, v...) + } +} + func (c *Client) get(path string, respData, reqData interface{}) (*Response, error) { return c.sendRequest(http.MethodGet, path, respData, reqData, false) } @@ -346,6 +392,7 @@ func (c *Client) doRequest(req *http.Request, resp *Response) error { c.setRequestHeaders(req) rateLimitFunc := c.opts.RateLimitFunc + tokenRefreshed := false for { if c.lastResponse != nil && rateLimitFunc != nil { @@ -355,6 +402,8 @@ func (c *Client) doRequest(req *http.Request, resp *Response) error { } } + c.logf("helix: request %s %s", req.Method, req.URL.String()) + response, err := c.opts.HTTPClient.Do(req) if err != nil { return fmt.Errorf("Failed to execute API request: %s", err.Error()) @@ -370,26 +419,38 @@ func (c *Client) doRequest(req *http.Request, resp *Response) error { return err } + c.logf("helix: response %d %s", response.StatusCode, string(bodyBytes)) + // Only attempt to decode the response if we have a response we can handle if len(bodyBytes) > 0 && resp.StatusCode < http.StatusInternalServerError { if resp.Data != nil && resp.StatusCode < http.StatusBadRequest { // Successful request err = json.Unmarshal(bodyBytes, &resp.Data) } else { - // A 401 means Twitch wants us to refresh our token: + // Failed request + err = json.Unmarshal(bodyBytes, &resp) + if err != nil { + return fmt.Errorf("Failed to decode API response: %s", err.Error()) + } + + // A 401 may mean Twitch wants us to refresh our token: // https://dev.twitch.tv/docs/authentication/refresh-tokens/ - if resp.StatusCode == http.StatusUnauthorized && c.canRefreshToken() { - if refreshErr := c.refreshToken(); refreshErr != nil { - log.Printf("Failed to refresh helix auth token: %v", refreshErr) - return err + // However, if the error is about missing scopes, refreshing + // won't help and would cause an infinite loop. + if resp.StatusCode == http.StatusUnauthorized && !tokenRefreshed && + !strings.HasPrefix(resp.ErrorMessage, "Missing scope") { + refreshed, refreshErr := c.tryRefreshToken() + if refreshErr != nil { + log.Printf("Failed to refresh helix token: %v", refreshErr) + break + } + if refreshed { + tokenRefreshed = true + // Try again now that we have a new token + c.setRequestHeaders(req) + continue } - // Try again now that we have a new token - c.setRequestHeaders(req) - continue } - - // Failed request - err = json.Unmarshal(bodyBytes, &resp) } if err != nil { @@ -424,6 +485,19 @@ func (c *Client) canRefreshToken() bool { (c.opts.ClientID != "" && c.opts.RefreshToken != "") } +// tryRefreshToken attempts to refresh whichever token type is currently in use. +// It returns true if a refresh was successfully performed, false if no refresh +// was applicable, and an error if a refresh was attempted but failed. +func (c *Client) tryRefreshToken() (bool, error) { + if c.canRefreshToken() { + return true, c.refreshToken() + } + if c.canRefreshAppToken() { + return true, c.refreshAppToken() + } + return false, nil +} + func (c *Client) refreshToken() error { resp, err := c.RefreshUserAccessToken(c.opts.RefreshToken) if err != nil || resp.StatusCode != http.StatusOK { @@ -448,6 +522,33 @@ func (c *Client) refreshToken() error { return nil } +func (c *Client) canRefreshAppToken() bool { + return c.opts.AppAccessToken != "" && c.opts.ClientID != "" && c.opts.ClientSecret != "" +} + +func (c *Client) refreshAppToken() error { + resp, err := c.RequestAppAccessToken(c.opts.AppAccessScopes) + if err != nil || resp.StatusCode != http.StatusOK { + statusCode := -1 + var errorMessage string + if resp != nil { + statusCode = resp.StatusCode + errorMessage = resp.ErrorMessage + } + return fmt.Errorf("failed to refresh app token: (%d: %s) %v", statusCode, errorMessage, err) + } + + c.mu.Lock() + c.opts.AppAccessToken = resp.Data.AccessToken + c.mu.Unlock() + + if cb := c.callbacks.onAppAccessTokenRefreshed; cb != nil { + go cb(resp.Data.AccessToken) + } + + return nil +} + func (c *Client) setRequestHeaders(req *http.Request) { opts := c.opts @@ -561,3 +662,9 @@ func (c *Client) OnUserAccessTokenRefreshed(f func(newAccessToken, newRefreshTok defer c.mu.Unlock() c.callbacks.onUserAccessTokenRefreshed = f } + +func (c *Client) OnAppAccessTokenRefreshed(f func(newAccessToken string)) { + c.mu.Lock() + defer c.mu.Unlock() + c.callbacks.onAppAccessTokenRefreshed = f +} diff --git a/src/vendor/github.com/nicklaw5/helix/v2/schedule.go b/src/vendor/github.com/nicklaw5/helix/v2/schedule.go index 61049f7..07f2313 100644 --- a/src/vendor/github.com/nicklaw5/helix/v2/schedule.go +++ b/src/vendor/github.com/nicklaw5/helix/v2/schedule.go @@ -96,7 +96,7 @@ func (c *Client) UpdateSchedule(params *UpdateScheduleParams) (*UpdateScheduleRe } type CreateScheduleSegmentParams struct { - BroadcasterID string `json:"broadcaster_id"` + BroadcasterID string `query:"broadcaster_id" json:"-"` StartTime Time `json:"start_time"` Timezone string `json:"timezone"` Duration string `json:"duration"` @@ -117,7 +117,7 @@ type CreateScheduleSegmentData struct { // Updates the broadcaster’s schedule settings, such as scheduling a vacation func (c *Client) CreateScheduleSegment(params *CreateScheduleSegmentParams) (*CreateScheduleSegmentResponse, error) { - resp, err := c.post("/schedule/segment", &CreateScheduleSegmentData{}, params) + resp, err := c.postAsJSON("/schedule/segment", &CreateScheduleSegmentData{}, params) if err != nil { return nil, err } @@ -130,8 +130,8 @@ func (c *Client) CreateScheduleSegment(params *CreateScheduleSegmentParams) (*Cr } type UpdateScheduleSegmentParams struct { - BroadcasterID string `json:"broadcaster_id"` - ID string `json:"id"` + BroadcasterID string `query:"broadcaster_id" json:"-"` + ID string `query:"id" json:"-"` StartTime Time `json:"start_time"` Duration string `json:"duration"` CategoryID string `json:"category_id"` @@ -165,8 +165,8 @@ func (c *Client) UpdateScheduleSegment(params *UpdateScheduleSegmentParams) (*Up } type DeleteScheduleSegmentParams struct { - BroadcasterID string `json:"broadcaster_id"` - ID string `json:"id"` + BroadcasterID string `query:"broadcaster_id"` + ID string `query:"id"` } type DeleteScheduleSegmentResponse struct { diff --git a/src/vendor/github.com/nicklaw5/helix/v2/teams.go b/src/vendor/github.com/nicklaw5/helix/v2/teams.go new file mode 100644 index 0000000..e13bb10 --- /dev/null +++ b/src/vendor/github.com/nicklaw5/helix/v2/teams.go @@ -0,0 +1,49 @@ +package helix + +type GetTeamsParams struct { + Name string `query:"name"` + ID string `query:"id"` +} + +type GetTeamsResponse struct { + ResponseCommon + Data ManyTeams +} + +type ManyTeams struct { + Teams []Team `json:"data"` +} + +type Team struct { + Users []TeamUser `json:"users"` + BackgroundImageURL string `json:"background_image_url"` + Banner string `json:"banner"` + CreatedAt Time `json:"created_at"` + UpdatedAt Time `json:"updated_at"` + Info string `json:"info"` + ThumbnailURL string `json:"thumbnail_url"` + TeamName string `json:"team_name"` + TeamDisplayName string `json:"team_display_name"` + ID string `json:"id"` +} + +type TeamUser struct { + UserID string `json:"user_id"` + UserLogin string `json:"user_login"` + UserName string `json:"user_name"` +} + +// GetTeams gets information about the specified Twitch team. +// Specify the team using the name or ID parameter (but not both). +func (c *Client) GetTeams(params *GetTeamsParams) (*GetTeamsResponse, error) { + resp, err := c.get("/teams", &ManyTeams{}, params) + if err != nil { + return nil, err + } + + teams := &GetTeamsResponse{} + resp.HydrateResponseCommon(&teams.ResponseCommon) + teams.Data.Teams = resp.Data.(*ManyTeams).Teams + + return teams, nil +} diff --git a/src/vendor/github.com/nicklaw5/helix/v2/videos.go b/src/vendor/github.com/nicklaw5/helix/v2/videos.go index 0b522ad..54989b0 100644 --- a/src/vendor/github.com/nicklaw5/helix/v2/videos.go +++ b/src/vendor/github.com/nicklaw5/helix/v2/videos.go @@ -22,7 +22,7 @@ type Video struct { type VideoMutedSegment struct { Duration int `json:"duration"` - Offest int `json:"offset"` + Offset int `json:"offset"` } type ManyVideos struct { diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt index 81ed38f..6293f24 100644 --- a/src/vendor/modules.txt +++ b/src/vendor/modules.txt @@ -138,7 +138,7 @@ github.com/kevinburke/ssh_config # github.com/klauspost/cpuid/v2 v2.3.0 ## explicit; go 1.22 github.com/klauspost/cpuid/v2 -# github.com/nicklaw5/helix/v2 v2.32.0 +# github.com/nicklaw5/helix/v2 v2.34.0 ## explicit; go 1.25 github.com/nicklaw5/helix/v2 # github.com/nikoksr/notify v1.5.0