Skip to content

Conversation

@Estiennejeremy
Copy link
Collaborator

@Estiennejeremy Estiennejeremy commented Feb 21, 2024

Summary by CodeRabbit

  • New Features

    • Introduced a comprehensive Twitch API integration with expanded support for channels, users, streams, games, clips, videos, subscriptions, moderation, extensions, custom rewards, and more.
    • Added Twitch OAuth2 authentication with token management, validation, automatic refreshing, and error handling.
    • Implemented extensive data models representing Twitch entities including clips, streams, users, games, cheermotes, redemptions, moderators, and custom rewards.
    • Enhanced API client with automatic token renewal, retry logic, and detailed response handling including rate limits and pagination.
    • Added rich Rake task metadata for improved task hierarchy and descriptions.
  • Chores

    • Added new dependencies for the Faraday HTTP client and related middleware.
    • Updated project configuration and metadata for improved dependency management and task organization.

<orderEntry type="library" scope="PROVIDED" name="activerecord (v7.1.3, Remote: ruby-3.2.3 (-p157) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="activestorage (v7.1.3, Remote: ruby-3.2.3 (-p157) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="activesupport (v7.1.3, Remote: ruby-3.2.3 (-p157) [gem]" level="application" />
<orderEntry type="library" scope="PROVIDED" name="awesome_print (v1.9.2, Remote: ruby-3.2.3 (-p157) [gem]" level="application" />
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oupsy va falloir mettre ca dans le gitignore

@victorsmits
Copy link
Owner

@coderabbitai full review

@victorsmits
Copy link
Owner

@coderabbitai summary

@coderabbitai
Copy link

coderabbitai bot commented May 15, 2025

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link

coderabbitai bot commented May 15, 2025

Walkthrough

This update introduces a comprehensive Twitch API client and supporting data models, error classes, and OAuth2 authentication logic in Ruby. It adds Faraday and related middleware as dependencies, cleans up project configuration, and provides modular, well-structured service classes for interacting with Twitch endpoints, handling OAuth2 token management, and modeling API responses.

Changes

File(s) Change Summary
.idea/twitchTracker.iml Removed several unused library dependencies, added net-http as provided, and fixed a test mailers path.
Gemfile Added faraday, faraday-parse_dates, and faraday-retry gems.
app/services/twitch-api.rb New entrypoint requiring Twitch version and client files.
app/services/twitch/api_error.rb Added Twitch::APIError class for API error encapsulation.
app/services/twitch/automod_message_status.rb Added Twitch::AutomodMessageStatus class for AutoMod message status.
app/services/twitch/bits_leader.rb Added Twitch::BitsLeader class to represent Bits leaderboard users.
app/services/twitch/category.rb Added Twitch::Category class for category data modeling.
app/services/twitch/channel.rb Added Twitch::Channel class for channel info modeling.
app/services/twitch/cheermote.rb, cheermote_tier.rb, cheermote_tier_image.rb, cheermote_tier_images.rb Added classes for Cheermote and tiered image modeling.
app/services/twitch/client.rb Added main Twitch::Client class with Faraday integration and API endpoint methods.
app/services/twitch/client/custom_rewards.rb, extensions.rb, games.rb, moderation.rb, streams.rb, subscriptions.rb, users.rb Added modular API endpoint wrappers for each Twitch API area.
app/services/twitch/clip.rb Added Twitch::Clip class for clip data.
app/services/twitch/custom_reward.rb Added Twitch::CustomReward class and nested image class.
app/services/twitch/editor.rb Added Twitch::Editor class for editor info.
app/services/twitch/entitlement_grant_url.rb Added Twitch::EntitlementGrantUrl class for entitlement URLs.
app/services/twitch/extension.rb Added Twitch::Extension class for extension data.
app/services/twitch/extensions_by_types.rb Added Twitch::ExtensionsByTypes class for extension grouping.
app/services/twitch/game.rb Added Twitch::Game class for game/category info.
app/services/twitch/game_analytic.rb Added Twitch::GameAnalytic class for analytics data.
app/services/twitch/moderation_event.rb Added Twitch::ModerationEvent class for moderation event data.
app/services/twitch/moderator.rb Added Twitch::Moderator class for moderator info.
app/services/twitch/redemption.rb Added Twitch::Redemption class for reward redemption events.
app/services/twitch/response.rb Added Twitch::Response class for API response handling, including rate limits and pagination.
app/services/twitch/stream.rb Added Twitch::Stream class for stream session info.
app/services/twitch/stream_marker.rb Added classes for stream marker and marker response modeling.
app/services/twitch/stream_metadata.rb Added Twitch::StreamMetadata class with dynamic metadata access.
app/services/twitch/subscription.rb Added Twitch::Subscription class for subscription info.
app/services/twitch/user.rb Added Twitch::User class for user info.
app/services/twitch/user_ban.rb Added Twitch::UserBan class for ban info.
app/services/twitch/user_follow.rb Added Twitch::UserFollow class for follow relationships.
app/services/twitch/version.rb Added Twitch module with VERSION constant.
app/services/twitch/video.rb Added Twitch::Video class for video info.
app/services/twitch_oauth2.rb New entrypoint requiring Twitch OAuth2 service files.
app/services/twitch_oauth2/authorize_error.rb Added TwitchOAuth2::AuthorizeError class for authorization errors.
app/services/twitch_oauth2/client.rb Added TwitchOAuth2::Client class for OAuth2 interactions.
app/services/twitch_oauth2/error.rb Added TwitchOAuth2::Error base error class.
app/services/twitch_oauth2/tokens.rb Added TwitchOAuth2::Tokens class for token management and validation.
app/services/twitch_oauth2/unsupported_token_type_error.rb Added TwitchOAuth2::UnsupportedTokenTypeError class.
app/services/twitch_oauth2/version.rb Added TwitchOAuth2 module with VERSION constant.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant Tokens as TwitchOAuth2::Tokens
    participant OAuth2 as TwitchOAuth2::Client
    participant Client as Twitch::Client
    participant Twitch as Twitch API

    App->>Tokens: Initialize with client credentials
    App->>Client: Initialize with Tokens
    App->>Client: Call API method (e.g., get_channels)
    Client->>Tokens: Request access_token
    Tokens->>OAuth2: Validate or refresh token as needed
    OAuth2->>Twitch: Send OAuth2 request (authorize/token/validate)
    Twitch-->>OAuth2: Respond with token or validation
    OAuth2-->>Tokens: Return token/validation result
    Tokens-->>Client: Return valid access_token
    Client->>Twitch: Make API request with token
    Twitch-->>Client: Respond with API data
    Client-->>App: Return parsed response object
Loading

Poem

In the meadow of code, a new Twitch breeze,
With Faraday’s help, we connect with ease.
OAuth2 tokens hop, never missing a beat,
Channels, clips, and streams—oh, what a treat!
Rabbits rejoice, for the API’s in sight,
With every new class, the future feels bright.
🐇✨

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between ab8c8dc and 1fe94c5.

⛔ Files ignored due to path filters (1)
  • Gemfile.lock is excluded by !**/*.lock
📒 Files selected for processing (48)
  • .idea/twitchTracker.iml (9 hunks)
  • Gemfile (1 hunks)
  • app/services/twitch-api.rb (1 hunks)
  • app/services/twitch/api_error.rb (1 hunks)
  • app/services/twitch/automod_message_status.rb (1 hunks)
  • app/services/twitch/bits_leader.rb (1 hunks)
  • app/services/twitch/category.rb (1 hunks)
  • app/services/twitch/channel.rb (1 hunks)
  • app/services/twitch/cheermote.rb (1 hunks)
  • app/services/twitch/cheermote_tier.rb (1 hunks)
  • app/services/twitch/cheermote_tier_image.rb (1 hunks)
  • app/services/twitch/cheermote_tier_images.rb (1 hunks)
  • app/services/twitch/client.rb (1 hunks)
  • app/services/twitch/client/custom_rewards.rb (1 hunks)
  • app/services/twitch/client/extensions.rb (1 hunks)
  • app/services/twitch/client/games.rb (1 hunks)
  • app/services/twitch/client/moderation.rb (1 hunks)
  • app/services/twitch/client/streams.rb (1 hunks)
  • app/services/twitch/client/subscriptions.rb (1 hunks)
  • app/services/twitch/client/users.rb (1 hunks)
  • app/services/twitch/clip.rb (1 hunks)
  • app/services/twitch/custom_reward.rb (1 hunks)
  • app/services/twitch/editor.rb (1 hunks)
  • app/services/twitch/entitlement_grant_url.rb (1 hunks)
  • app/services/twitch/extension.rb (1 hunks)
  • app/services/twitch/extensions_by_types.rb (1 hunks)
  • app/services/twitch/game.rb (1 hunks)
  • app/services/twitch/game_analytic.rb (1 hunks)
  • app/services/twitch/moderation_event.rb (1 hunks)
  • app/services/twitch/moderator.rb (1 hunks)
  • app/services/twitch/redemption.rb (1 hunks)
  • app/services/twitch/response.rb (1 hunks)
  • app/services/twitch/stream.rb (1 hunks)
  • app/services/twitch/stream_marker.rb (1 hunks)
  • app/services/twitch/stream_metadata.rb (1 hunks)
  • app/services/twitch/subscription.rb (1 hunks)
  • app/services/twitch/user.rb (1 hunks)
  • app/services/twitch/user_ban.rb (1 hunks)
  • app/services/twitch/user_follow.rb (1 hunks)
  • app/services/twitch/version.rb (1 hunks)
  • app/services/twitch/video.rb (1 hunks)
  • app/services/twitch_oauth2.rb (1 hunks)
  • app/services/twitch_oauth2/authorize_error.rb (1 hunks)
  • app/services/twitch_oauth2/client.rb (1 hunks)
  • app/services/twitch_oauth2/error.rb (1 hunks)
  • app/services/twitch_oauth2/tokens.rb (1 hunks)
  • app/services/twitch_oauth2/unsupported_token_type_error.rb (1 hunks)
  • app/services/twitch_oauth2/version.rb (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai
Copy link

coderabbitai bot commented May 15, 2025

✅ Actions performed

Summary regeneration triggered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 31

🔭 Outside diff range comments (1)
app/services/twitch-api.rb (1)

1-5: ⚠️ Potential issue

Filename should follow Ruby/Rails conventions
The file is named twitch-api.rb, but Ruby and Rails autoload expect snake_case filenames (e.g., twitch_api.rb). Please rename the file to twitch_api.rb to ensure predictable loading, and update any require_relative or require calls accordingly.

♻️ Duplicate comments (1)
.idea/twitchTracker.iml (1)

293-549: IDE configuration files should be in .gitignore

IDE-specific configuration files like this should typically be excluded from version control to avoid repository pollution with user-specific settings. Add this directory to your .gitignore file.

🧹 Nitpick comments (42)
app/services/twitch/extension.rb (2)

31-35: Whitelist attributes in initializer to avoid unintended ivar assignments
Using instance_variable_set on every key in the input hash can introduce unexpected instance variables if the API returns extra fields. Consider filtering the attributes to a known list:

# Add at top of class
VALID_ATTRIBUTES = %i[id version name can_activate type active x y].freeze

def initialize(attributes = {})
  attributes.slice(*VALID_ATTRIBUTES).each do |key, value|
    instance_variable_set(:"@#{key}", value)
  end
end

This ensures only expected properties are assigned.


1-37: Add unit tests for Twitch::Extension
No test coverage exists for this new domain object. It’s important to verify that each attribute is correctly mapped and that unsupported keys are ignored. Would you like assistance generating a spec file for this class?

app/services/twitch_oauth2/error.rb (1)

3-7: Good base error class structure, but consider consistent indentation.

The error class is appropriately defined as a subclass of StandardError, which is the recommended approach for application-specific exceptions. However, the indentation uses tabs instead of the Ruby community standard of 2 spaces.

module TwitchOAuth2
-	## Error during Twitch OAuth2 operations
-	class Error < StandardError
-	end
+  ## Error during Twitch OAuth2 operations
+  class Error < StandardError
+  end
end
app/services/twitch_oauth2/unsupported_token_type_error.rb (1)

3-10: Well-structured error class, but indentation inconsistency detected.

The error class correctly inherits from the base Error class and provides a descriptive error message with the specific token type. This approach enhances debugging by providing clear context about the issue.

module TwitchOAuth2
-	## Error with `:token_type` recognition
-	class UnsupportedTokenTypeError < Error
-		def initialize(token_type)
-			super("Unsupported token type: `#{token_type}`")
-		end
-	end
+  ## Error with `:token_type` recognition
+  class UnsupportedTokenTypeError < Error
+    def initialize(token_type)
+      super("Unsupported token type: `#{token_type}`")
+    end
+  end
end
app/services/twitch/game_analytic.rb (1)

9-14: Consider consistent attribute naming for API mappings.

The implementation correctly maps API response attributes to Ruby object attributes. However, note that the URL attribute from the API uses uppercase while Ruby conventions typically use lowercase or snake_case.

Either document why the API returns mixed-case keys or consider normalizing the case in the initializer:

def initialize(attributes = {})
  @game_id = attributes['game_id']
-  @url = attributes['URL']
+  # Map from 'URL' in the API response
+  @url = attributes['URL'] || attributes['url']
end
app/services/twitch/editor.rb (1)

6-13: Consider using YARD-style docs for attribute readers.
Switching from ## comments to # @return [Type] description improves IDE/tooling support and consistency with Ruby documentation conventions.

app/services/twitch/cheermote_tier_image.rb (1)

12-16: Validate expected keys (animated & static) in initializer.
To prevent silent failures when the API shape changes, explicitly guard against missing or unexpected keys:

def initialize(attributes = {})
  %w[animated static].each do |key|
    raise ArgumentError, "Missing key: #{key}" unless attributes.key?(key)
    instance_variable_set("@#{key}", attributes[key])
  end
end
app/services/twitch/automod_message_status.rb (1)

3-17: Well-structured data model class.

The AutomodMessageStatus class effectively models Twitch's AutoMod message status with appropriate attributes and documentation. The dynamic attribute assignment in the constructor is flexible for handling API responses.

Consider enhancing this class with:

  1. Type validation for critical attributes
  2. A method to serialize the object back to a hash/JSON for API responses
 def initialize(attributes = {})
   attributes.each do |key, value|
     instance_variable_set :"@#{key}", value
   end
 end
+
+# Returns a hash representation of this object
+def to_h
+  {
+    msg_id: @msg_id,
+    is_permitted: @is_permitted
+  }
+end
app/services/twitch/channel.rb (1)

3-45: Well-documented data object with comprehensive attribute coverage.

The Channel class properly documents each attribute with detailed descriptions matching the Twitch API specification. The documentation includes explanations of special formats (like RFC3339 for timestamps) and field behaviors.

For further improvement, consider:

  1. Adding a method to handle the different endpoint variations mentioned in the comment on line 26
  2. Including validation for critical fields
  3. Adding helper methods for common operations (like formatting timestamps)
 def initialize(attributes = {})
   attributes.each do |key, value|
     instance_variable_set :"@#{key}", value
   end
 end
+
+# Returns true if the channel is currently streaming
+def streaming?
+  !!is_live
+end
+
+# Returns the start time as a Time object instead of string
+def started_at_time
+  Time.parse(started_at) if started_at && !started_at.empty?
+end
app/services/twitch/game.rb (2)

3-22: Properly structured game data model with explicit attribute assignment.

The Game class clearly represents a Twitch game/category with appropriate attributes. Unlike the other data models, this class uses explicit attribute assignment rather than dynamic assignment, which is more predictable.

Consider adding a helper method to generate a fully formed box art URL with specified dimensions:

 def initialize(attributes = {})
   @id = attributes['id']
   @name = attributes['name']
   @box_art_url = attributes['box_art_url']
 end
+
+# Generate a complete box art URL with the specified dimensions
+# @param width [Integer] desired width in pixels
+# @param height [Integer] desired height in pixels
+# @return [String] fully formed URL
+def box_art_url_with_size(width, height)
+  return nil unless box_art_url
+  box_art_url.gsub('{width}', width.to_s).gsub('{height}', height.to_s)
+end

17-21: Inconsistent attribute assignment approach.

This class uses a different attribute assignment approach compared to the other data models in this PR (Channel and AutomodMessageStatus), which use dynamic assignment.

For consistency with other models, consider either:

  1. Using the same dynamic assignment approach:
 def initialize(attributes = {})
-  @id = attributes['id']
-  @name = attributes['name']
-  @box_art_url = attributes['box_art_url']
+  attributes.each do |key, value|
+    instance_variable_set :"@#{key}", value
+  end
 end
  1. Document why this class uses a different approach if there's a specific reason
app/services/twitch/client/subscriptions.rb (1)

7-9: Consider plural method name for consistency.

The method name uses singular "subscription" but likely returns multiple subscription objects.

Consider renaming to match the expected response structure:

-def get_broadcaster_subscription(options = {})
+def get_broadcaster_subscriptions(options = {})
   initialize_response Subscription, post('subscriptions', options)
 end
app/services/twitch_oauth2/authorize_error.rb (1)

3-13: Consistent indentation needed and consider improving error message clarity

The class implementation is clean and focused, but has a few minor issues:

  1. The indentation uses tabs instead of spaces (lines 5-12), which is inconsistent with Ruby style guidelines that typically recommend 2 spaces.
  2. The error message uses backticks within a string that might be confusing as they won't render as code formatting in plain text error contexts.
 module TwitchOAuth2
-	## Error for `#authorize_link`
-	class AuthorizeError < Error
-		attr_reader :link
+  ## Error for `#authorize_link`
+  class AuthorizeError < Error
+    attr_reader :link
 
-		def initialize(link)
-			@link = link
-			super('Direct user to `error.link` and assign `code`')
-		end
-	end
+    def initialize(link)
+      @link = link
+      super('Direct user to the error link and assign a code')
+    end
+  end
 end
app/services/twitch/bits_leader.rb (1)

16-21: Consider adding type handling and default values

The model implementation is clear but could be more robust by adding default values and type conversions.

 def initialize(attributes = {})
-  @user_id = attributes['user_id']
-  @user_name = attributes['user_name']
-  @rank = attributes['rank']
-  @score = attributes['score']
+  @user_id = attributes['user_id']
+  @user_name = attributes['user_name']
+  @rank = attributes['rank'].to_i if attributes['rank']
+  @score = attributes['score'].to_i if attributes['score']
 end

This ensures numeric fields are properly converted to integers, making it safer to use in calculations.

app/services/twitch/stream.rb (1)

5-35: Consider implementing common object methods

The class currently lacks methods like to_s or inspect that would be helpful for debugging and logging.

For better debugging and string representation, consider adding:

def to_s
  "#<Twitch::Stream id=#{id} user_name='#{user_name}' title='#{title}'>"
end

# Alias to_s to inspect for better console output
alias inspect to_s

This provides a clean, informative output when the object is printed or inspected in console or logs.

app/services/twitch/client/users.rb (4)

5-6: Minor grammatical correction needed in the comment

The comment says "API method for users" but the module contains multiple methods. Consider updating it to "API methods for users" for accuracy.

-    ## API method for users
+    ## API methods for users

7-9: Add documentation for the parameter

The method lacks documentation for what options are expected/supported. Consider adding a comment to clarify which parameters can be passed in the options hash.

     module Users
+      # Gets information about users who follow other users.
+      # @param options [Hash] Optional parameters:
+      #   @option options [String] :from_id User ID of the follower. Can be used to specify only this user follows.
+      #   @option options [String] :to_id User ID of the broadcaster being followed. Can be used to specify only followers for this user.
+      #   @option options [Integer] :first Maximum number of records to return (default: 20, max: 100)
+      #   @option options [String] :after Cursor for pagination
       def get_users_follows(options = {})
         initialize_response UserFollow, get('users/follows', options)
       end

11-13: Add documentation for the parameter

The method lacks documentation for what options are expected/supported. Consider adding a comment to clarify which parameters can be passed in the options hash.

+      # Gets information about one or more specified Twitch users.
+      # @param options [Hash] Optional parameters:
+      #   @option options [String] :id User ID. Multiple IDs can be specified using an array.
+      #   @option options [String] :login User login name. Multiple names can be specified using an array.
       def get_users(options = {})
         initialize_response User, get('users', options)
       end

15-17: Add documentation for the parameter

The method lacks documentation for what options are expected/supported. Consider adding a comment to clarify which parameters can be passed in the options hash.

+      # Updates a user's description.
+      # @param options [Hash] Required parameters:
+      #   @option options [String] :description User's new description
       def update_user(options = {})
         initialize_response User, put('users', options)
       end
app/services/twitch/client/games.rb (4)

5-6: Minor grammatical correction needed in the comment

The comment says "API method for games" but the module contains multiple methods. Consider updating it to "API methods for games" for accuracy.

-    ## API method for games
+    ## API methods for games

7-9: Add documentation for the parameter

The method lacks documentation for what options are expected/supported. Consider adding a comment to clarify which parameters can be passed in the options hash.

     module Games
+      # Gets game information by game ID or name.
+      # @param options [Hash] Optional parameters:
+      #   @option options [String, Array<String>] :id Game ID. Multiple IDs can be specified.
+      #   @option options [String, Array<String>] :name Game name. Multiple names can be specified.
       def get_games(options = {})
         initialize_response Game, get('games', options)
       end

11-13: Add documentation for the parameter

The method lacks documentation for what options are expected/supported. Consider adding a comment to clarify which parameters can be passed in the options hash.

+      # Gets games sorted by number of current viewers on Twitch, most popular first.
+      # @param options [Hash] Optional parameters:
+      #   @option options [Integer] :first Maximum number of objects to return (default: 20, max: 100)
+      #   @option options [String] :after Cursor for pagination
+      #   @option options [String] :before Cursor for pagination
       def get_top_games(options = {})
         initialize_response Game, get('games/top', options)
       end

15-17: Add documentation for the parameter

The method lacks documentation for what options are expected/supported. Consider adding a comment to clarify which parameters can be passed in the options hash.

+      # Gets game analytics reports for games.
+      # @param options [Hash] Optional parameters:
+      #   @option options [String] :game_id Game ID. Multiple IDs can be specified using an array.
+      #   @option options [String] :type Type of analytics report ("overview_v2")
+      #   @option options [String] :started_at Starting date/time for returned reports
+      #   @option options [String] :ended_at Ending date/time for returned reports
+      #   @option options [Integer] :first Maximum number of objects to return (default: 20, max: 100)
+      #   @option options [String] :after Cursor for pagination
       def get_game_analytics(options = {})
         initialize_response GameAnalytic, get('analytics/games', options)
       end
app/services/twitch/extensions_by_types.rb (3)

7-9: Clarify attribute documentation

The documentation for the attributes explains that they are dictionaries but doesn't explicitly mention that they contain Extension objects. Consider updating the documentation to make this clear.

-    # A dictionary that contains the data for a panel extension.
+    # A dictionary that contains Extension objects for panel extensions.
     # The dictionary's key is a sequential number beginning with 1.
-    # The following fields contain the panel's data for each key.
+    # Each value is an Extension object containing the panel's data.

11-14: Clarify attribute documentation

The documentation for the attributes explains that they are dictionaries but doesn't explicitly mention that they contain Extension objects. Consider updating the documentation to make this clear.

-    # A dictionary that contains the data for a video-overlay extension.
+    # A dictionary that contains Extension objects for video-overlay extensions.
     # The dictionary's key is a sequential number beginning with 1.
-    # The following fields contain the overlay's data for each key.
+    # Each value is an Extension object containing the overlay's data.

16-19: Clarify attribute documentation

The documentation for the attributes explains that they are dictionaries but doesn't explicitly mention that they contain Extension objects. Consider updating the documentation to make this clear.

-    # A dictionary that contains the data for a video-component extension.
+    # A dictionary that contains Extension objects for video-component extensions.
     # The dictionary's key is a sequential number beginning with 1.
-    # The following fields contain the component's data for each key.
+    # Each value is an Extension object containing the component's data.
app/services/twitch/user.rb (2)

30-34: Consider adding validation for attributes

The initializer sets any key from the attributes hash as an instance variable without validation. Consider adding validation to ensure only expected attributes are set.

   def initialize(attributes = {})
-    attributes.each do |key, value|
+    attributes.slice(*%w[id login display_name type broadcaster_type description 
+                         profile_image_url offline_image_url view_count created_at]).each do |key, value|
       instance_variable_set :"@#{key}", value
     end
   end

Alternatively, you could define a list of valid attributes and check against it:

def initialize(attributes = {})
  valid_attributes = %w[id login display_name type broadcaster_type description 
                       profile_image_url offline_image_url view_count created_at]
  
  attributes.each do |key, value|
    if valid_attributes.include?(key.to_s)
      instance_variable_set :"@#{key}", value
    end
  end
end

26-27: Add data type clarification in documentation

For view_count, it would be helpful to specify the data type (likely an Integer). This provides clarity on what to expect when accessing this attribute.

-    # Total number of visits to the user's stream page.
+    # Total number of visits to the user's stream page (Integer).
     attr_reader :view_count
app/services/twitch/subscription.rb (1)

22-26: Consider type conversion for boolean and numeric fields.

For added robustness, you might want to consider converting certain attributes to their appropriate types:

 def initialize(attributes = {})
   attributes.each do |key, value|
+    value = !!value if key.to_s == 'is_gift'
+    value = value.to_i if key.to_s == 'tier'
     instance_variable_set :"@#{key}", value
   end
 end
app/services/twitch/category.rb (1)

4-20: Documentation style inconsistency.

The comment style in this file uses double hash (##) while other files in the module use single hash (#). Consider standardizing the documentation style across all files.

-  ## Data class for categories
+  # Data class for categories
   class Category
-    ## A URL to an image of the game's box art or streaming category.
+    # A URL to an image of the game's box art or streaming category.
     attr_reader :box_art_url

-    ## The name of the game or category.
+    # The name of the game or category.
     attr_reader :name

-    ## 	An ID that uniquely identifies the game or category.
+    # An ID that uniquely identifies the game or category.
     attr_reader :id

-    ## Not always available:
+    # Not always available:

-    ## The ID that IGDB uses to identify this game.
-    ## If the IGDB ID is not available to Twitch, this field is set to an empty string.
+    # The ID that IGDB uses to identify this game.
+    # If the IGDB ID is not available to Twitch, this field is set to an empty string.
     attr_reader :igdb_id
app/services/twitch/redemption.rb (2)

4-5: Incorrect class documentation.

The class comment incorrectly describes this as "Data object for Twitch users" when it's actually for redemptions.

-  # Data object for Twitch users
+  # Data object for Twitch redemptions
   class Redemption

8-9: Typo in documentation.

There's a typo in the documentation for broadcaster_login.

-    # Unformatted (lowercase) username of thebroadcaster.
+    # Unformatted (lowercase) username of the broadcaster.
     attr_reader :broadcaster_login
app/services/twitch/clip.rb (1)

1-41: Clean data model with good documentation

The Twitch::Clip class is well-structured with clear documentation for each attribute. The implementation follows a common pattern for Ruby data models with read-only attributes.

One suggestion for improvement would be to consider adding type conversion for certain attributes:

 def initialize(attributes = {})
   attributes.each do |key, value|
+    # Convert string timestamps to Time objects
+    value = Time.parse(value) if key == 'created_at' && value.is_a?(String)
     instance_variable_set :"@#{key}", value
   end
 end

This would ensure that the created_at field is always a Time object, making it easier to perform date calculations and comparisons.

app/services/twitch/video.rb (1)

39-43: Consider adding timestamp conversion

Similar to the Clip class, you should consider adding explicit type conversion for timestamp fields:

 def initialize(attributes = {})
   attributes.each do |key, value|
+    # Convert string timestamps to Time objects
+    value = Time.parse(value) if ['created_at', 'published_at'].include?(key) && value.is_a?(String)
     instance_variable_set :"@#{key}", value
   end
 end

This would ensure that created_at and published_at are always Time objects, making them more useful for date manipulations.

app/services/twitch/client/extensions.rb (1)

15-17: Consider adding documentation for parameters

While the method implementation is good, it would be helpful to add documentation describing what options are available and required:

+      # Updates a user's extension configuration
+      # @param options [Hash] Options for the request
+      # @option options [String] :user_id The ID of the user whose extensions will be modified
+      # @option options [Hash] :extension_data A hash containing extension configuration details
 def update_user_extensions(options = {})
   initialize_response ExtensionsByTypes, put('users/extensions', options)
 end

This would make the client API more self-documenting and easier to use.

app/services/twitch_oauth2/tokens.rb (1)

87-93: Handle missing expires_in gracefully in assign_tokens

-@expires_at = Time.now + data[:expires_in]
+expires_in = data.fetch(:expires_in, 0).to_i
+@expires_at = expires_in.positive? ? Time.now + expires_in : Time.at(Float::INFINITY)

This avoids TypeError when expires_in is absent and clearly signals
non-expiring tokens.

app/services/twitch/response.rb (1)

1-81: Well-structured wrapper for Twitch API responses

This class provides a comprehensive wrapper for Twitch API responses, with clear documentation and proper separation of concerns. It extracts various metadata including rate limits, pagination, and correctly instantiates data objects.

However, there are a few potential improvements:

  1. Consider adding error handling for malformed JSON responses or missing fields
  2. The parse_data method could use more robust handling for edge cases (nil data)
 def parse_data(data_class)
   raw_data = body['data']
+  return nil if raw_data.nil?
   return raw_data unless data_class

   if raw_data.is_a?(Array)
     raw_data.map { |data_element| data_class.new(data_element) }
   else
     data_class.new(raw_data)
   end
 end
app/services/twitch/client/streams.rb (1)

1-29: Add documentation for method parameters

The methods in this module lack documentation for the expected parameters in the options hash. Consider adding RDoc or YARD-style documentation to improve developer experience.

For example:

+  # Creates a stream marker at the current timestamp
+  # @param options [Hash] Options for the request
+  # @option options [String] :user_id Required. ID of the broadcaster
+  # @option options [String] :description Optional. Description for the marker
+  # @return [Twitch::Response] containing StreamMarker data
   def create_stream_marker(options = {})
     initialize_response StreamMarker, post('streams/markers', options)
   end
app/services/twitch_oauth2/client.rb (1)

45-58: Handle missing parameters in token request gracefully

The token request parameters should be filtered to remove nil values, as the API may not handle them gracefully.

 def token(token_type:, code: nil)
+  params = {
+    client_id: @client_id,
+    client_secret: @client_secret,
+    code: code,
+    grant_type: grant_type_by_token_type(token_type),
+    redirect_uri: @redirect_uri
+  }.compact
+
   response = CONNECTION.post(
     'token',
-    client_id: @client_id,
-    client_secret: @client_secret,
-    code: code,
-    grant_type: grant_type_by_token_type(token_type),
-    redirect_uri: @redirect_uri
+    params
   )

   return response.body if response.success?

   raise Error, response.body[:message]
 end
.idea/twitchTracker.iml (1)

280-281: Remove duplicate test/mailers/previews entry

There's a duplicate entry for the test/mailers/previews path which is unnecessary.

 <entry key="test/mailers/previews">
   <value>file://$MODULE_DIR$/test/mailers/previews</value>
-  <value>file://$MODULE_DIR$/test/mailers/previews</value>
 </entry>
app/services/twitch/custom_reward.rb (1)

22-70: Consider predicate helpers for Boolean flags.

Ruby conventionally uses predicate methods (enabled?, paused?, etc.). Exposing both raw attributes and friendly helpers makes the API nicer to consume:

def enabled? = @is_enabled
def paused?  = @is_paused

(Not a blocker, just a quality-of-life enhancement.)

app/services/twitch/client.rb (1)

162-174: Retry logic: consider exponential back-off & limiting non-auth retries.

Right now we retry only once on 401.
Faraday already retries on network exceptions, but for HTTP errors you may want:

  1. Configurable max attempts.
  2. Exponential back-off / jitter to respect Twitch rate limits.
  3. Handling of 429 (rate-limit) or 5xx responses.

Not required for MVP, but worth tracking.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between ab8c8dc and 1fe94c5.

⛔ Files ignored due to path filters (1)
  • Gemfile.lock is excluded by !**/*.lock
📒 Files selected for processing (48)
  • .idea/twitchTracker.iml (9 hunks)
  • Gemfile (1 hunks)
  • app/services/twitch-api.rb (1 hunks)
  • app/services/twitch/api_error.rb (1 hunks)
  • app/services/twitch/automod_message_status.rb (1 hunks)
  • app/services/twitch/bits_leader.rb (1 hunks)
  • app/services/twitch/category.rb (1 hunks)
  • app/services/twitch/channel.rb (1 hunks)
  • app/services/twitch/cheermote.rb (1 hunks)
  • app/services/twitch/cheermote_tier.rb (1 hunks)
  • app/services/twitch/cheermote_tier_image.rb (1 hunks)
  • app/services/twitch/cheermote_tier_images.rb (1 hunks)
  • app/services/twitch/client.rb (1 hunks)
  • app/services/twitch/client/custom_rewards.rb (1 hunks)
  • app/services/twitch/client/extensions.rb (1 hunks)
  • app/services/twitch/client/games.rb (1 hunks)
  • app/services/twitch/client/moderation.rb (1 hunks)
  • app/services/twitch/client/streams.rb (1 hunks)
  • app/services/twitch/client/subscriptions.rb (1 hunks)
  • app/services/twitch/client/users.rb (1 hunks)
  • app/services/twitch/clip.rb (1 hunks)
  • app/services/twitch/custom_reward.rb (1 hunks)
  • app/services/twitch/editor.rb (1 hunks)
  • app/services/twitch/entitlement_grant_url.rb (1 hunks)
  • app/services/twitch/extension.rb (1 hunks)
  • app/services/twitch/extensions_by_types.rb (1 hunks)
  • app/services/twitch/game.rb (1 hunks)
  • app/services/twitch/game_analytic.rb (1 hunks)
  • app/services/twitch/moderation_event.rb (1 hunks)
  • app/services/twitch/moderator.rb (1 hunks)
  • app/services/twitch/redemption.rb (1 hunks)
  • app/services/twitch/response.rb (1 hunks)
  • app/services/twitch/stream.rb (1 hunks)
  • app/services/twitch/stream_marker.rb (1 hunks)
  • app/services/twitch/stream_metadata.rb (1 hunks)
  • app/services/twitch/subscription.rb (1 hunks)
  • app/services/twitch/user.rb (1 hunks)
  • app/services/twitch/user_ban.rb (1 hunks)
  • app/services/twitch/user_follow.rb (1 hunks)
  • app/services/twitch/version.rb (1 hunks)
  • app/services/twitch/video.rb (1 hunks)
  • app/services/twitch_oauth2.rb (1 hunks)
  • app/services/twitch_oauth2/authorize_error.rb (1 hunks)
  • app/services/twitch_oauth2/client.rb (1 hunks)
  • app/services/twitch_oauth2/error.rb (1 hunks)
  • app/services/twitch_oauth2/tokens.rb (1 hunks)
  • app/services/twitch_oauth2/unsupported_token_type_error.rb (1 hunks)
  • app/services/twitch_oauth2/version.rb (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (14)
app/services/twitch_oauth2/unsupported_token_type_error.rb (1)
app/services/twitch_oauth2/tokens.rb (1)
  • initialize (17-30)
app/services/twitch/client/extensions.rb (1)
app/services/twitch/client.rb (1)
  • initialize_response (148-150)
app/services/twitch/extensions_by_types.rb (1)
app/services/twitch/extension.rb (2)
  • attr_reader (5-36)
  • initialize (31-35)
app/services/twitch/client/users.rb (1)
app/services/twitch/client.rb (1)
  • initialize_response (148-150)
app/services/twitch/cheermote_tier_image.rb (3)
app/services/twitch/cheermote.rb (2)
  • attr_reader (7-52)
  • initialize (45-51)
app/services/twitch/cheermote_tier.rb (2)
  • attr_reader (9-54)
  • initialize (47-53)
app/services/twitch/cheermote_tier_images.rb (2)
  • attr_reader (7-20)
  • initialize (15-19)
app/services/twitch/client/subscriptions.rb (1)
app/services/twitch/client.rb (1)
  • initialize_response (148-150)
app/services/twitch/client/games.rb (1)
app/services/twitch/client.rb (1)
  • initialize_response (148-150)
app/services/twitch/client/custom_rewards.rb (1)
app/services/twitch/client.rb (1)
  • initialize_response (148-150)
app/services/twitch/client/moderation.rb (1)
app/services/twitch/client.rb (1)
  • initialize_response (148-150)
app/services/twitch/cheermote_tier_images.rb (2)
app/services/twitch/cheermote_tier.rb (2)
  • attr_reader (9-54)
  • initialize (47-53)
app/services/twitch/cheermote_tier_image.rb (2)
  • attr_reader (5-17)
  • initialize (12-16)
app/services/twitch_oauth2/client.rb (3)
app/services/twitch_oauth2/authorize_error.rb (2)
  • attr_reader (5-12)
  • initialize (8-11)
app/services/twitch_oauth2/tokens.rb (4)
  • attr_reader (7-94)
  • initialize (17-30)
  • code (54-56)
  • access_token (48-52)
app/services/twitch_oauth2/unsupported_token_type_error.rb (2)
  • initialize (5-9)
  • initialize (6-8)
app/services/twitch/client/streams.rb (1)
app/services/twitch/client.rb (1)
  • initialize_response (148-150)
app/services/twitch/cheermote_tier.rb (3)
app/services/twitch/cheermote.rb (2)
  • attr_reader (7-52)
  • initialize (45-51)
app/services/twitch/cheermote_tier_image.rb (2)
  • attr_reader (5-17)
  • initialize (12-16)
app/services/twitch/cheermote_tier_images.rb (2)
  • attr_reader (7-20)
  • initialize (15-19)
app/services/twitch/client.rb (3)
app/services/twitch/api_error.rb (2)
  • attr_reader (5-17)
  • initialize (11-16)
app/services/twitch/entitlement_grant_url.rb (2)
  • attr_reader (5-13)
  • initialize (10-12)
app/services/twitch_oauth2/tokens.rb (1)
  • access_token (48-52)
🔇 Additional comments (20)
Gemfile (1)

60-65: ⚠️ Potential issue

Typo in gem name for retry middleware
The gem "farady-retry" appears to be misspelled—it should be "faraday-retry". This will prevent Bundler from finding the correct package.
Apply this diff to fix the typo:

- gem "farady-retry", "~> 2.2"
+ gem "faraday-retry", "~> 2.2"

Likely an incorrect or invalid review comment.

app/services/twitch/version.rb (1)

1-6: Version module is properly defined
The Twitch::VERSION constant is clear and follows expected patterns. No changes required here.

app/services/twitch_oauth2/version.rb (1)

1-6: OAuth2 version module is correctly declared
The TwitchOAuth2::VERSION constant is well-structured and consistent with the other version file.

app/services/twitch/entitlement_grant_url.rb (1)

3-13: Clean implementation with good documentation.

The class is well-structured with appropriate documentation referring to the Twitch API. The attribute and initialization pattern follows Ruby conventions.

app/services/twitch/moderator.rb (1)

7-17: Data model looks good and consistent.
The dynamic assignment pattern matches other Twitch entity classes, and the attributes cover the needed fields.

app/services/twitch/stream.rb (1)

3-4: LGTM - Good practice importing Time library

Properly importing the Time library for timestamp handling is important for working with API date fields.

app/services/twitch/subscription.rb (1)

1-28: Well-structured subscription data model.

The Subscription class provides a clean implementation with well-documented attributes and follows Ruby conventions for API response objects.

app/services/twitch/category.rb (1)

1-27: Well-structured category data model.

The Category class provides a clean implementation with appropriate attributes and follows the same pattern as other data models.

app/services/twitch/redemption.rb (1)

29-34: Well-structured redemption data model.

The initialization logic is clean and consistent with other data models in the module.

app/services/twitch/stream_metadata.rb (2)

19-35: Good approach for dynamic metadata access.

The implementation properly stores and accesses dynamic game-specific metadata attributes, and the method_missing implementation correctly delegates to super for unknown methods.


3-18: Excellent documentation of dynamic behavior.

The class documentation clearly explains how the dynamic attribute access works and what users of this class should expect.

app/services/twitch/video.rb (2)

1-4: Good use of required libraries

Properly importing the time library which will be necessary for handling datetime attributes.


5-38: Well-structured video model with comprehensive attributes

The Twitch::Video class provides a complete representation of a Twitch video with thorough documentation for each attribute.

app/services/twitch/cheermote_tier_images.rb (3)

3-4: Appropriate use of relative requires

Good practice using relative requires for related model classes.


5-14: Clear documentation of complex data structure

The class documentation clearly explains the nested structure of the cheermote tier images, which is helpful for understanding how to use this model.


15-19: Object composition pattern implemented well

The implementation correctly creates CheermoteTierImage instances for each theme, providing a clean object structure that matches the API data format.

app/services/twitch/client/extensions.rb (2)

3-9: Well-structured API client module

The Extensions module follows a good pattern for organizing related API methods. The method get_user_extensions is appropriately named and follows the pattern used elsewhere in the client.


11-13: Consistent method implementation

The get_user_active_extensions method follows the same pattern as the previous method, providing consistency in the API.

.idea/twitchTracker.iml (1)

73-73: Add net-http gem dependency

Good addition of the net-http gem dependency which will be needed for the Twitch API client.

app/services/twitch/client.rb (1)

158-170: Refreshing the token should also update the header on the new connection.

After refactoring to an instance connection, remember to update @connection.headers['Authorization'] instead of the old constant.

def renew_authorization_header
  @connection.headers['Authorization'] = "Bearer #{tokens.access_token}"
end

Failing to do so will leave the stale token in place and the second request will still 401.

Comment on lines +3 to +4
require_relative 'twitch_oauth2/error'
require_relative 'twitch_oauth2/unsupported_token_type_error'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing require for authorize_error.rb.
The summary and TwitchOAuth2::Tokens logic reference AuthorizeError, but this file does not load its definition. Add the missing require to ensure the error class is available at runtime.

Apply this diff:

 require_relative 'twitch_oauth2/error'
+require_relative 'twitch_oauth2/authorize_error'
 require_relative 'twitch_oauth2/unsupported_token_type_error'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
require_relative 'twitch_oauth2/error'
require_relative 'twitch_oauth2/unsupported_token_type_error'
require_relative 'twitch_oauth2/error'
require_relative 'twitch_oauth2/authorize_error'
require_relative 'twitch_oauth2/unsupported_token_type_error'
🤖 Prompt for AI Agents
In app/services/twitch_oauth2.rb around lines 3 to 4, the file is missing a
require statement for 'authorize_error.rb', which defines the AuthorizeError
class referenced in the summary and TwitchOAuth2::Tokens logic. Add a
require_relative line for 'twitch_oauth2/authorize_error' alongside the existing
require_relative statements to load the error class and prevent runtime errors.

Comment on lines +15 to +19
def initialize(attributes = {})
attributes.each do |key, value|
instance_variable_set :"@#{key}", value
end
end
Copy link

@coderabbitai coderabbitai bot May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Parse created_at into a Time object for usability.
Right now created_at remains a raw string. Parsing it in the initializer will make downstream date calculations safer and more ergonomic:

+ require 'time'
   def initialize(attributes = {})
     attributes.each do |key, value|
-      instance_variable_set :"@#{key}", value
+      parsed = (key.to_s == 'created_at' ? Time.parse(value) : value)
+      instance_variable_set :"@#{key}", parsed
     end
   end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def initialize(attributes = {})
attributes.each do |key, value|
instance_variable_set :"@#{key}", value
end
end
require 'time'
def initialize(attributes = {})
attributes.each do |key, value|
parsed = (key.to_s == 'created_at' ? Time.parse(value) : value)
instance_variable_set :"@#{key}", parsed
end
end
🤖 Prompt for AI Agents
In app/services/twitch/editor.rb around lines 15 to 19, the initialize method
sets instance variables directly from attributes, leaving created_at as a raw
string. Modify the initializer to detect if the key is created_at and parse its
value into a Time object before setting the instance variable. This ensures
created_at is stored as a Time instance for safer and easier date operations
downstream.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can explain me this in more stupid way

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Estiennejeremy Sure, let me explain this in a simpler way:

Right now, your code takes the created_at value exactly as it comes from Twitch, which is a text string like "2023-04-15T14:30:00Z".

The suggestion is to automatically convert this string into a proper Ruby Time object when you create an Editor.

Why is this helpful?

  • With a string, if you want to know "how long ago did this person become an editor?" you'd need to do extra work to convert the string to a date first
  • With a Time object, you can easily do things like Time.now - created_at to get the difference in seconds

How the fix works:

  1. Add require 'time' at the top to use Ruby's time parsing
  2. When setting instance variables, check if the current one is 'created_at'
  3. If it is, convert the string to a Time object with Time.parse
  4. Otherwise, leave the value as-is

This makes the created_at value more useful right away without you having to convert it every time you want to use it for date calculations.

Comment on lines +11 to +16
def initialize(status_code, body)
@status_code = status_code
@body = body

super(self.body['message'])
end
Copy link

@coderabbitai coderabbitai bot May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard against missing or non-hash body['message'].
Calling body['message'] directly can blow up if body isn’t a Hash or lacks message. Fallback to a safe representation:

     def initialize(status_code, body)
       @status_code = status_code
       @body = body

-      super(self.body['message'])
+      msg = body.is_a?(Hash) ? (body['message'] || body.to_s) : body.to_s
+      super(msg)
     end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def initialize(status_code, body)
@status_code = status_code
@body = body
super(self.body['message'])
end
def initialize(status_code, body)
@status_code = status_code
@body = body
msg = body.is_a?(Hash) ? (body['message'] || body.to_s) : body.to_s
super(msg)
end
🤖 Prompt for AI Agents
In app/services/twitch/api_error.rb around lines 11 to 16, the code directly
accesses body['message'] which can cause errors if body is not a Hash or does
not contain the 'message' key. To fix this, add a guard to safely check if body
is a Hash and has the 'message' key before accessing it; otherwise, provide a
fallback string or the raw body content when calling super to avoid exceptions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabiitai can explain me this in more stupid way

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +7 to +9
def get_broadcaster_subscription(options = {})
initialize_response Subscription, post('subscriptions', options)
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Missing parameter documentation.

The get_broadcaster_subscription method accepts an options hash, but there's no documentation about what parameters are expected or required.

Add documentation for the expected parameters:

+    # Get subscriptions for a broadcaster
+    # 
+    # @param options [Hash] Parameters for the request
+    # @option options [String] :broadcaster_id Required. ID of the broadcaster.
+    # @option options [String] :user_id Optional. Filters results to subscriptions for a specific user.
+    # @return [Twitch::Response<Twitch::Subscription>] Response containing subscription information
     def get_broadcaster_subscription(options = {})
       initialize_response Subscription, post('subscriptions', options)
     end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_broadcaster_subscription(options = {})
initialize_response Subscription, post('subscriptions', options)
end
# Get subscriptions for a broadcaster
#
# @param options [Hash] Parameters for the request
# @option options [String] :broadcaster_id Required. ID of the broadcaster.
# @option options [String] :user_id Optional. Filters results to subscriptions for a specific user.
# @return [Twitch::Response<Twitch::Subscription>] Response containing subscription information
def get_broadcaster_subscription(options = {})
initialize_response Subscription, post('subscriptions', options)
end
🤖 Prompt for AI Agents
In app/services/twitch/client/subscriptions.rb around lines 7 to 9, the method
get_broadcaster_subscription accepts an options hash but lacks documentation on
the expected parameters. Add a comment above the method describing the options
hash keys, their types, and whether they are required or optional to clarify
what parameters the method expects.

Comment on lines +6 to +10
module Subscriptions
def get_broadcaster_subscription(options = {})
initialize_response Subscription, post('subscriptions', options)
end
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

HTTP method mismatch in get_broadcaster_subscription.

The method is named with a 'get' prefix but uses a POST request, which is unconventional and may be confusing. Most REST APIs use GET requests for retrieval operations.

Verify if the Twitch API really requires POST for this endpoint:


🏁 Script executed:

#!/bin/bash
# Check if Twitch API documentation specifies POST for subscriptions endpoint
curl -s "https://dev.twitch.tv/docs/api/reference" | grep -A 10 "subscriptions" | grep -i "method" || echo "Method not found in basic search"

# Look for any mentions of subscriptions with POST in Twitch API docs
curl -s "https://dev.twitch.tv/docs/api/reference" | grep -A 5 -B 5 "POST.*subscriptions" || echo "No direct mentions found"

Length of output: 318


Rename misleading HTTP method in subscription client

The get_broadcaster_subscription method actually issues a POST to create an EventSub subscription (POST /eventsub/subscriptions), so its get_ prefix is misleading. According to Twitch’s EventSub API:

  • Creating a subscription uses POST
  • Listing subscriptions uses GET

Please rename this method to reflect its behavior—e.g. create_broadcaster_subscription or subscribe_to_broadcaster.

Locations to update:

  • File: app/services/twitch/client/subscriptions.rb
  • Lines: 6–10

Suggested diff:

--- a/app/services/twitch/client/subscriptions.rb
+++ b/app/services/twitch/client/subscriptions.rb
 module Subscriptions
-  def get_broadcaster_subscription(options = {})
+  def create_broadcaster_subscription(options = {})
     initialize_response Subscription, post('subscriptions', options)
   end
 end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
module Subscriptions
def get_broadcaster_subscription(options = {})
initialize_response Subscription, post('subscriptions', options)
end
end
module Subscriptions
- def get_broadcaster_subscription(options = {})
+ def create_broadcaster_subscription(options = {})
initialize_response Subscription, post('subscriptions', options)
end
end
🤖 Prompt for AI Agents
In app/services/twitch/client/subscriptions.rb around lines 6 to 10, the method
named get_broadcaster_subscription uses a POST request which is misleading given
the 'get_' prefix. Rename the method to create_broadcaster_subscription or
subscribe_to_broadcaster to accurately reflect that it creates a subscription
via POST. Update all references to this method accordingly to maintain
consistency.

Comment on lines +1 to +92
# frozen_string_literal: true

require 'faraday'
require 'faraday/parse_dates'
require 'securerandom'

module TwitchOAuth2
## Client for making requests
class Client
CONNECTION = Faraday.new(
url: 'https://id.twitch.tv/oauth2/'
) do |connection|
connection.request :json

connection.response :parse_dates

connection.response :json,
content_type: /\bjson$/,
parser_options: { symbolize_names: true }
end.freeze

attr_reader :client_id

def initialize(client_id:, client_secret:, redirect_uri: 'http://localhost')
@client_id = client_id
@client_secret = client_secret
@redirect_uri = redirect_uri
end

def authorize(scopes:)
response = CONNECTION.get(
'authorize',
client_id: @client_id,
redirect_uri: @redirect_uri,
scope: Array(scopes).join(' '),
response_type: :code
)

location = response.headers[:location]
return location if location

raise Error, response.body[:message]
end

def token(token_type:, code: nil)
response = CONNECTION.post(
'token',
client_id: @client_id,
client_secret: @client_secret,
code: code,
grant_type: grant_type_by_token_type(token_type),
redirect_uri: @redirect_uri
)

return response.body if response.success?

raise Error, response.body[:message]
end

def validate(access_token:)
response = CONNECTION.get(
'validate', {}, { 'Authorization' => "OAuth #{access_token}" }
)

response.body
end

def refresh(refresh_token:)
response = CONNECTION.post(
'token',
client_id: @client_id,
client_secret: @client_secret,
grant_type: :refresh_token,
refresh_token: refresh_token
)

return response.body if response.success?

raise Error, response.body[:message]
end

private

def grant_type_by_token_type(token_type)
case token_type
when :user then :authorization_code
when :application then :client_credentials
else raise UnsupportedTokenTypeError, token_type
end
end
end
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use consistent indentation (spaces vs tabs)

Your indentation uses tabs while Ruby convention and the rest of the codebase uses spaces. This inconsistency can lead to formatting issues when viewed across different editors.

Convert all tab indentation to spaces (typically 2 spaces per indentation level in Ruby) to maintain consistency with Ruby conventions.

🤖 Prompt for AI Agents
In app/services/twitch_oauth2/client.rb lines 1 to 92, the code uses tabs for
indentation, which is inconsistent with Ruby conventions and the rest of the
codebase that use spaces. Replace all tab characters with spaces, using 2 spaces
per indentation level, to ensure consistent formatting and adherence to Ruby
style guidelines.

Comment on lines +10 to +20
CONNECTION = Faraday.new(
url: 'https://id.twitch.tv/oauth2/'
) do |connection|
connection.request :json

connection.response :parse_dates

connection.response :json,
content_type: /\bjson$/,
parser_options: { symbolize_names: true }
end.freeze
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add request timeout configuration to Faraday connection

The Faraday connection lacks timeout settings, which could lead to hanging requests if the Twitch API is slow to respond or unavailable.

 CONNECTION = Faraday.new(
   url: 'https://id.twitch.tv/oauth2/'
 ) do |connection|
   connection.request :json
+  connection.options.timeout = 15        # seconds for the entire request
+  connection.options.open_timeout = 5    # seconds to open the connection

   connection.response :parse_dates

   connection.response :json,
     content_type: /\bjson$/,
     parser_options: { symbolize_names: true }
 end.freeze
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CONNECTION = Faraday.new(
url: 'https://id.twitch.tv/oauth2/'
) do |connection|
connection.request :json
connection.response :parse_dates
connection.response :json,
content_type: /\bjson$/,
parser_options: { symbolize_names: true }
end.freeze
CONNECTION = Faraday.new(
url: 'https://id.twitch.tv/oauth2/'
) do |connection|
connection.request :json
connection.options.timeout = 15 # seconds for the entire request
connection.options.open_timeout = 5 # seconds to open the connection
connection.response :parse_dates
connection.response :json,
content_type: /\bjson$/,
parser_options: { symbolize_names: true }
end.freeze
🤖 Prompt for AI Agents
In app/services/twitch_oauth2/client.rb around lines 10 to 20, the Faraday
connection does not have timeout settings configured, which can cause requests
to hang indefinitely if the Twitch API is slow or unresponsive. Add timeout
options such as open_timeout and read_timeout to the Faraday.new configuration
block to specify maximum wait times for opening a connection and reading a
response.

Comment on lines +80 to +89
IMAGE_ATTRS = %w[image default_image].freeze
private_constant :IMAGE_ATTRS

def initialize(attributes = {})
attributes.each do |key, value|
value = CustomRewardImage.new(value) if IMAGE_ATTRS.include?(key) && !value.nil?

instance_variable_set :"@#{key}", value
end
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

CustomReward#initialize repeats the same mass-assignment pitfall.

For the top-level reward object we again copy every field verbatim, which may include unexpected or future Twitch fields that could clash with internal state. Prefer the same whitelist strategy as above or use dry-struct/ostruct-like coercion libraries to keep this safer and clearer.
This will also make it easier to validate types (Strings vs Integers vs Booleans) up-front instead of scattering that responsibility throughout the codebase.

🤖 Prompt for AI Agents
In app/services/twitch/custom_reward.rb around lines 80 to 89, the initialize
method currently assigns all attributes directly, risking conflicts with
unexpected or future Twitch fields. To fix this, implement a whitelist of
allowed attribute keys and only assign those, optionally using a coercion
library like dry-struct or ostruct for type validation. This approach ensures
safer, clearer initialization and centralizes type checks.

Comment on lines +15 to +19
def initialize(attributes = {})
attributes.each do |key, value|
instance_variable_set :"@#{key}", value
end
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid broad instance_variable_set — restrict to known attributes.

Blindly copying every key/value from the API response into instance variables can accidentally create attributes that are not part of the public contract, hide bugs, or open a surface for malicious payloads (e.g. overriding @__send__). Consider whitelisting the expected keys instead:

-attributes.each do |key, value|
-  instance_variable_set :"@#{key}", value
-end
+%w[url_1x url_2x url_4x].each do |attr|
+  instance_variable_set("@#{attr}", attributes[attr]) if attributes.key?(attr)
+end
+unknown = attributes.keys - %w[url_1x url_2x url_4x]
+raise ArgumentError, "Unknown attribute(s): #{unknown.join(', ')}" unless unknown.empty?
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def initialize(attributes = {})
attributes.each do |key, value|
instance_variable_set :"@#{key}", value
end
end
def initialize(attributes = {})
- attributes.each do |key, value|
- instance_variable_set :"@#{key}", value
- end
+ %w[url_1x url_2x url_4x].each do |attr|
+ instance_variable_set("@#{attr}", attributes[attr]) if attributes.key?(attr)
+ end
+ unknown = attributes.keys - %w[url_1x url_2x url_4x]
+ raise ArgumentError, "Unknown attribute(s): #{unknown.join(', ')}" unless unknown.empty?
end
🤖 Prompt for AI Agents
In app/services/twitch/custom_reward.rb around lines 15 to 19, the initialize
method uses instance_variable_set on all keys from attributes, which can create
unintended or unsafe instance variables. To fix this, define a whitelist of
expected attribute keys and only set instance variables for those keys, ignoring
or rejecting any others to ensure only known attributes are assigned.

Comment on lines +38 to +65
# Base connection to Helix API.
CONNECTION = Faraday.new(
'https://api.twitch.tv/helix', {
headers: { 'User-Agent': "twitch-api ruby client #{Twitch::VERSION}" }
}
) do |faraday|
faraday.request :retry,
exceptions: [*Faraday::Retry::Middleware::DEFAULT_EXCEPTIONS, Faraday::ConnectionFailed]

faraday.response :parse_dates

faraday.request :json
faraday.response :json
end

attr_reader :tokens

# Initializes a Twitch client.
#
# - tokens [TwitchOAuth2::Tokens] Tokens object with their refreshing logic inside.
# All client and authentication information (`client_id`, `:scopes`, etc.) stores there.
def initialize(tokens:)
@tokens = tokens

CONNECTION.headers['Client-ID'] = self.tokens.client.client_id

renew_authorization_header
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Shared CONNECTION constant causes cross-instance header leakage and thread-safety issues.

Because all clients reuse the same Faraday object, the last instantiated client overwrites the Client-ID & Authorization headers for every other client in the process.
Typical failure modes:
• Multi-tenant apps sending requests with the wrong bearer token.
• Race conditions in threaded environments.

Refactor to build the connection per instance:

-class Client
-  # Base connection to Helix API.
-  CONNECTION = Faraday.new('https://api.twitch.tv/helix', headers: { 'User-Agent': "twitch-api ruby client #{Twitch::VERSION}" }) do |faraday|
+class Client
+  def build_connection
+    Faraday.new('https://api.twitch.tv/helix',
+                headers: { 'User-Agent': "twitch-api ruby client #{Twitch::VERSION}",
+                           'Client-ID':  tokens.client.client_id,
+                           'Authorization': "Bearer #{tokens.access_token}" }) do |faraday|
       faraday.request  :retry, exceptions: [*Faraday::Retry::Middleware::DEFAULT_EXCEPTIONS, Faraday::ConnectionFailed]
       faraday.response :parse_dates
       faraday.request  :json
       faraday.response :json
     end
   end

Then store it in @connection and use that everywhere (request, renew_authorization_header, etc.).
This isolates headers per client instance and is safe under concurrency.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/services/twitch/client.rb between lines 38 and 65, the CONNECTION
constant is a shared Faraday instance causing header leakage and thread-safety
issues across client instances. Refactor by moving the Faraday.new connection
setup into the initialize method to create a new connection per instance, assign
it to an instance variable like @connection, and update all uses of CONNECTION
to use @connection instead. This ensures headers like Client-ID and
Authorization are isolated per client and safe for concurrent use.

@Estiennejeremy Estiennejeremy requested a review from Copilot May 15, 2025 12:39
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a comprehensive base module for Twitch API integration, encapsulating data models and API client functionality to interact with various Twitch endpoints.

  • Adds data models for Twitch entities such as Clip, Channel, Cheermote, Category, etc.
  • Implements API client methods grouped by functionality (users, streams, games, moderation, etc.) and introduces error handling and token management.
  • Updates Gemfile with the necessary dependencies to support the new HTTP client and middleware.

Reviewed Changes

Copilot reviewed 49 out of 49 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
app/services/twitch/clip.rb Adds a data model for Twitch clips.
app/services/twitch/client/users.rb Adds API methods for user-related endpoints.
app/services/twitch/client/subscriptions.rb Adds subscription API methods.
app/services/twitch/client/streams.rb Adds stream-related API methods (including a marker and a metadata method marked as TODO).
app/services/twitch/client/moderation.rb Adds moderation-related API methods.
app/services/twitch/client/games.rb Implements game-related API methods.
app/services/twitch/client/extensions.rb Implements extension-related API methods.
app/services/twitch/client/custom_rewards.rb Provides client methods for custom rewards endpoints.
app/services/twitch/client.rb Implements the core Twitch client including request handling and dynamic HTTP method definitions.
app/services/twitch/cheermote*.rb Establishes data models for Cheermotes and associated tier images.
app/services/twitch/channel.rb Defines a data model for Twitch channels.
app/services/twitch/category.rb Adds a data model for Twitch categories.
app/services/twitch/bits_leader.rb Defines the bits leader data model.
app/services/twitch/automod_message_status.rb Adds a data model for AutoMod message status.
app/services/twitch/api_error.rb Implements error handling for Twitch API responses.
app/services/twitch-api.rb Entry point for the Twitch API integration.
Gemfile Updates dependency definitions to support Faraday and related middleware.

Comment on lines +36 to +38
attributes.each do |key, value|
instance_variable_set :"@#{key}", value
end
Copy link

Copilot AI May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider explicitly assigning known attributes instead of using instance_variable_set for improved code clarity and error checking, if dynamic assignment is not a strict requirement.

Suggested change
attributes.each do |key, value|
instance_variable_set :"@#{key}", value
end
@id = attributes[:id]
@title = attributes[:title]
@created_at = attributes[:created_at]
@url = attributes[:url]
@thumbnail_url = attributes[:thumbnail_url]
@embed_url = attributes[:embed_url]
@view_count = attributes[:view_count]
@language = attributes[:language]
@broadcaster_id = attributes[:broadcaster_id]
@broadcaster_name = attributes[:broadcaster_name]
@creator_id = attributes[:creator_id]
@creator_name = attributes[:creator_name]
@game_id = attributes[:game_id]
@video_id = attributes[:video_id]

Copilot uses AI. Check for mistakes.
end

## https://dev.twitch.tv/docs/api/reference#modify-channel-information
def modify_channel(options = {})
Copy link

Copilot AI May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The modify_channel method returns inconsistent types (boolean when response.body is empty and response.body otherwise); standardize the return type to improve API consistency for consumers.

Copilot uses AI. Check for mistakes.
@status_code = status_code
@body = body

super(self.body['message'])
Copy link

Copilot AI May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the response body includes a 'message' key or provide a default error message to avoid passing nil to StandardError.

Suggested change
super(self.body['message'])
super(self.body&.dig('message') || "An error occurred")

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants