Class: Discordrb::Commands::Bucket

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

Overview

This class represents a bucket for rate limiting - it keeps track of how many requests have been made and when exactly the user should be rate limited.

Instance Method Summary collapse

Constructor Details

#initialize(limit, time_span, delay) ⇒ Bucket

Makes a new bucket

Parameters:

  • limit (Integer, nil)

    How many requests the user may perform in the given time_span, or nil if there should be no limit.

  • time_span (Integer, nil)

    The time span after which the request count is reset, in seconds, or nil if the bucket should never be reset. (If this is nil, limit should be nil too)

  • delay (Integer, nil)

    The delay for which the user has to wait after performing a request, in seconds, or nil if the user shouldn't have to wait.

Raises:

  • (ArgumentError)


11
12
13
14
15
16
17
18
19
# File 'lib/discordrb/commands/rate_limiter.rb', line 11

def initialize(limit, time_span, delay)
  raise ArgumentError, '`limit` and `time_span` have to either both be set or both be nil!' if !limit != !time_span

  @limit = limit
  @time_span = time_span
  @delay = delay

  @bucket = {}
end

Instance Method Details

#clean(rate_limit_time = nil) ⇒ Object

Cleans the bucket, removing all elements that aren't necessary anymore

Parameters:

  • rate_limit_time (Time) (defaults to: nil)

    The time to base the cleaning on, only useful for testing.



23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/discordrb/commands/rate_limiter.rb', line 23

def clean(rate_limit_time = nil)
  rate_limit_time ||= Time.now

  @bucket.delete_if do |_, limit_hash|
    # Time limit has not run out
    return false if @time_span && rate_limit_time < (limit_hash[:set_time] + @time_span)

    # Delay has not run out
    return false if @delay && rate_limit_time < (limit_hash[:last_time] + @delay)

    true
  end
end

#rate_limited?(thing, rate_limit_time = nil, increment: 1) ⇒ Integer, false

Performs a rate limiting request

Parameters:

  • thing (#resolve_id, Integer, Symbol)

    The particular thing that should be rate-limited (usually a user/channel, but you can also choose arbitrary integers or symbols)

  • rate_limit_time (Time) (defaults to: nil)

    The time to base the rate limiting on, only useful for testing.

  • increment (Integer) (defaults to: 1)

    How much to increment the rate-limit counter. Default is 1.

Returns:

  • (Integer, false)

    the waiting time until the next request, in seconds, or false if the request succeeded



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/discordrb/commands/rate_limiter.rb', line 42

def rate_limited?(thing, rate_limit_time = nil, increment: 1)
  key = resolve_key thing
  limit_hash = @bucket[key]

  # First case: limit_hash doesn't exist yet
  unless limit_hash
    @bucket[key] = {
      last_time: Time.now,
      set_time: Time.now,
      count: increment
    }

    return false
  end

  # Define the time at which we're being rate limited once so it doesn't get inaccurate
  rate_limit_time ||= Time.now

  if @limit && (limit_hash[:count] + increment) > @limit
    # Second case: Count is over the limit and the time has not run out yet
    return (limit_hash[:set_time] + @time_span) - rate_limit_time if @time_span && rate_limit_time < (limit_hash[:set_time] + @time_span)

    # Third case: Count is over the limit but the time has run out
    # Don't return anything here because there may still be delay-based limiting
    limit_hash[:set_time] = rate_limit_time
    limit_hash[:count] = 0
  end

  if @delay && rate_limit_time < (limit_hash[:last_time] + @delay)
    # Fourth case: we're being delayed
    (limit_hash[:last_time] + @delay) - rate_limit_time
  else
    # Fifth case: no rate limiting at all! Increment the count, set the last_time, and return false
    limit_hash[:last_time] = rate_limit_time
    limit_hash[:count] += increment
    false
  end
end