Class: Twitch::RateLimiter

Inherits:
Object
  • Object
show all
Defined in:
lib/twitch/rate_limiter.rb

Constant Summary collapse

LIMIT_HEADER =

Twitch API rate limit header names

"ratelimit-limit".freeze
REMAINING_HEADER =
"ratelimit-remaining".freeze
RESET_HEADER =
"ratelimit-reset".freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(logger: nil) ⇒ RateLimiter

Returns a new instance of RateLimiter.



10
11
12
13
14
15
16
# File 'lib/twitch/rate_limiter.rb', line 10

def initialize(logger: nil)
  @limit = nil
  @remaining = nil
  @reset_at = nil
  @logger = logger
  @last_warn_at = nil
end

Instance Attribute Details

#limitObject (readonly)

Returns the value of attribute limit.



8
9
10
# File 'lib/twitch/rate_limiter.rb', line 8

def limit
  @limit
end

#loggerObject (readonly)

Returns the value of attribute logger.



8
9
10
# File 'lib/twitch/rate_limiter.rb', line 8

def logger
  @logger
end

#remainingObject (readonly)

Returns the value of attribute remaining.



8
9
10
# File 'lib/twitch/rate_limiter.rb', line 8

def remaining
  @remaining
end

#reset_atObject (readonly)

Returns the value of attribute reset_at.



8
9
10
# File 'lib/twitch/rate_limiter.rb', line 8

def reset_at
  @reset_at
end

Instance Method Details

#approaching_limit?(threshold: 10) ⇒ Boolean

Check if we’re approaching the rate limit

Returns:

  • (Boolean)


28
29
30
31
32
# File 'lib/twitch/rate_limiter.rb', line 28

def approaching_limit?(threshold: 10)
  return false if remaining.nil? || limit.nil?

  remaining <= threshold
end

#rate_limited?Boolean

Check if rate limited (no remaining requests)

Returns:

  • (Boolean)


43
44
45
# File 'lib/twitch/rate_limiter.rb', line 43

def rate_limited?
  remaining == 0
end

#resetObject

Reset rate limit tracking



74
75
76
77
78
79
# File 'lib/twitch/rate_limiter.rb', line 74

def reset
  @limit = nil
  @remaining = nil
  @reset_at = nil
  @last_warn_at = nil
end

#reset_inObject

Get seconds until rate limit resets



35
36
37
38
39
40
# File 'lib/twitch/rate_limiter.rb', line 35

def reset_in
  return nil if reset_at.nil?

  seconds = reset_at - Time.now.to_i
  [ seconds, 0 ].max  # Ensure non-negative
end

#statusObject

Get formatted status string



82
83
84
85
86
87
88
# File 'lib/twitch/rate_limiter.rb', line 82

def status
  if limit.nil? || remaining.nil?
    "Rate limit info not available"
  else
    "#{remaining}/#{limit} requests remaining"
  end
end

#update(response_headers) ⇒ Object

Update rate limit info from response headers



19
20
21
22
23
24
25
# File 'lib/twitch/rate_limiter.rb', line 19

def update(response_headers)
  return unless response_headers

  @limit = response_headers[LIMIT_HEADER]&.to_i
  @remaining = response_headers[REMAINING_HEADER]&.to_i
  @reset_at = response_headers[RESET_HEADER]&.to_i
end

#wait_if_rate_limited(base_wait: 1.0, max_wait: 60.0) ⇒ Object

Wait if rate limited, with exponential backoff



61
62
63
64
65
66
67
68
69
70
71
# File 'lib/twitch/rate_limiter.rb', line 61

def wait_if_rate_limited(base_wait: 1.0, max_wait: 60.0)
  return if !rate_limited? || reset_in.nil?

  wait_seconds = [ reset_in + 1, max_wait ].min  # Add 1 second buffer

  if logger
    logger.warn("Rate limited. Waiting #{wait_seconds} seconds until reset at #{Time.at(reset_at)}")
  end

  sleep(wait_seconds)
end

#warn_if_approaching(threshold: 10) ⇒ Object

Log rate limit warning if approaching threshold



48
49
50
51
52
53
54
55
56
57
58
# File 'lib/twitch/rate_limiter.rb', line 48

def warn_if_approaching(threshold: 10)
  return unless approaching_limit?(threshold: threshold)
  return unless logger
  return if recently_warned?

  @last_warn_at = Time.now
  logger.warn(
    "Twitch API rate limit approaching: #{remaining}/#{limit} requests remaining. " \
    "Resets in #{reset_in} seconds."
  )
end