-
Notifications
You must be signed in to change notification settings - Fork 0
Add base twitch api module #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| <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" /> |
There was a problem hiding this comment.
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
|
@coderabbitai full review |
|
@coderabbitai summary |
✅ Actions performedFull review triggered. |
WalkthroughThis 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
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
Poem
Note ⚡️ AI Code Reviews for VS Code, Cursor, WindsurfCodeRabbit 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. Note ⚡️ Faster reviews with cachingCodeRabbit 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 📜 Recent review detailsConfiguration used: CodeRabbit UI ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (48)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
✅ Actions performedSummary regeneration triggered. |
There was a problem hiding this 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 issueFilename should follow Ruby/Rails conventions
The file is namedtwitch-api.rb, but Ruby and Rails autoload expect snake_case filenames (e.g.,twitch_api.rb). Please rename the file totwitch_api.rbto ensure predictable loading, and update anyrequire_relativeorrequirecalls accordingly.
♻️ Duplicate comments (1)
.idea/twitchTracker.iml (1)
293-549: IDE configuration files should be in .gitignoreIDE-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
Usinginstance_variable_seton 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 endThis ensures only expected properties are assigned.
1-37: Add unit tests forTwitch::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 endapp/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 endapp/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
URLattribute 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'] endapp/services/twitch/editor.rb (1)
6-13: Consider using YARD-style docs for attribute readers.
Switching from##comments to# @return [Type] descriptionimproves 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 endapp/services/twitch/automod_message_status.rb (1)
3-17: Well-structured data model class.The
AutomodMessageStatusclass 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:
- Type validation for critical attributes
- 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 + } +endapp/services/twitch/channel.rb (1)
3-45: Well-documented data object with comprehensive attribute coverage.The
Channelclass 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:
- Adding a method to handle the different endpoint variations mentioned in the comment on line 26
- Including validation for critical fields
- 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? +endapp/services/twitch/game.rb (2)
3-22: Properly structured game data model with explicit attribute assignment.The
Gameclass 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 (
ChannelandAutomodMessageStatus), which use dynamic assignment.For consistency with other models, consider either:
- 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
- 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) endapp/services/twitch_oauth2/authorize_error.rb (1)
3-13: Consistent indentation needed and consider improving error message clarityThe class implementation is clean and focused, but has a few minor issues:
- The indentation uses tabs instead of spaces (lines 5-12), which is inconsistent with Ruby style guidelines that typically recommend 2 spaces.
- 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 endapp/services/twitch/bits_leader.rb (1)
16-21: Consider adding type handling and default valuesThe 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'] endThis 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 methodsThe class currently lacks methods like
to_sorinspectthat 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_sThis 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 commentThe 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 parameterThe 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 parameterThe 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 parameterThe 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) endapp/services/twitch/client/games.rb (4)
5-6: Minor grammatical correction needed in the commentThe 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 parameterThe 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 parameterThe 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 parameterThe 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) endapp/services/twitch/extensions_by_types.rb (3)
7-9: Clarify attribute documentationThe documentation for the attributes explains that they are dictionaries but doesn't explicitly mention that they contain
Extensionobjects. 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 documentationThe documentation for the attributes explains that they are dictionaries but doesn't explicitly mention that they contain
Extensionobjects. 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 documentationThe documentation for the attributes explains that they are dictionaries but doesn't explicitly mention that they contain
Extensionobjects. 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 attributesThe 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 endAlternatively, 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 documentationFor
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_countapp/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 endapp/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_idapp/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_loginapp/services/twitch/clip.rb (1)
1-41: Clean data model with good documentationThe
Twitch::Clipclass 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 endThis would ensure that the
created_atfield 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 conversionSimilar to the
Clipclass, 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 endThis would ensure that
created_atandpublished_atare always Time objects, making them more useful for date manipulations.app/services/twitch/client/extensions.rb (1)
15-17: Consider adding documentation for parametersWhile 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) endThis would make the client API more self-documenting and easier to use.
app/services/twitch_oauth2/tokens.rb (1)
87-93: Handle missingexpires_ingracefully inassign_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
TypeErrorwhenexpires_inis absent and clearly signals
non-expiring tokens.app/services/twitch/response.rb (1)
1-81: Well-structured wrapper for Twitch API responsesThis 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:
- Consider adding error handling for malformed JSON responses or missing fields
- The
parse_datamethod 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 endapp/services/twitch/client/streams.rb (1)
1-29: Add documentation for method parametersThe 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) endapp/services/twitch_oauth2/client.rb (1)
45-58: Handle missing parameters in token request gracefullyThe 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 entryThere'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:
- Configurable max attempts.
- Exponential back-off / jitter to respect Twitch rate limits.
- 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
⛔ Files ignored due to path filters (1)
Gemfile.lockis 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 issueTypo 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
TheTwitch::VERSIONconstant 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
TheTwitchOAuth2::VERSIONconstant 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 libraryProperly 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
Subscriptionclass 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
Categoryclass 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_missingimplementation correctly delegates tosuperfor 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 librariesProperly importing the
timelibrary which will be necessary for handling datetime attributes.
5-38: Well-structured video model with comprehensive attributesThe
Twitch::Videoclass 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 requiresGood practice using relative requires for related model classes.
5-14: Clear documentation of complex data structureThe 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 wellThe implementation correctly creates
CheermoteTierImageinstances 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 moduleThe Extensions module follows a good pattern for organizing related API methods. The method
get_user_extensionsis appropriately named and follows the pattern used elsewhere in the client.
11-13: Consistent method implementationThe
get_user_active_extensionsmethod follows the same pattern as the previous method, providing consistency in the API..idea/twitchTracker.iml (1)
73-73: Add net-http gem dependencyGood 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}" endFailing to do so will leave the stale token in place and the second request will still 401.
| require_relative 'twitch_oauth2/error' | ||
| require_relative 'twitch_oauth2/unsupported_token_type_error' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| def initialize(attributes = {}) | ||
| attributes.each do |key, value| | ||
| instance_variable_set :"@#{key}", value | ||
| end | ||
| end |
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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_atto get the difference in seconds
How the fix works:
- Add
require 'time'at the top to use Ruby's time parsing - When setting instance variables, check if the current one is 'created_at'
- If it is, convert the string to a Time object with
Time.parse - 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.
| def initialize(status_code, body) | ||
| @status_code = status_code | ||
| @body = body | ||
|
|
||
| super(self.body['message']) | ||
| end |
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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!
| def get_broadcaster_subscription(options = {}) | ||
| initialize_response Subscription, post('subscriptions', options) | ||
| end |
There was a problem hiding this comment.
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.
| 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.
| module Subscriptions | ||
| def get_broadcaster_subscription(options = {}) | ||
| initialize_response Subscription, post('subscriptions', options) | ||
| end | ||
| end |
There was a problem hiding this comment.
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.
| 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.
| # 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 |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
| 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.
| 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 |
There was a problem hiding this comment.
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.
| def initialize(attributes = {}) | ||
| attributes.each do |key, value| | ||
| instance_variable_set :"@#{key}", value | ||
| end | ||
| end |
There was a problem hiding this comment.
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.
| 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.
| # 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
endThen 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.
There was a problem hiding this 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. |
| attributes.each do |key, value| | ||
| instance_variable_set :"@#{key}", value | ||
| end |
Copilot
AI
May 15, 2025
There was a problem hiding this comment.
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.
| 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] |
| end | ||
|
|
||
| ## https://dev.twitch.tv/docs/api/reference#modify-channel-information | ||
| def modify_channel(options = {}) |
Copilot
AI
May 15, 2025
There was a problem hiding this comment.
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.
| @status_code = status_code | ||
| @body = body | ||
|
|
||
| super(self.body['message']) |
Copilot
AI
May 15, 2025
There was a problem hiding this comment.
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.
| super(self.body['message']) | |
| super(self.body&.dig('message') || "An error occurred") |
Summary by CodeRabbit
New Features
Chores