Method: Discordrb::API.request

Defined in:
lib/discordrb/api.rb

.request(key, major_parameter, type, *attributes) ⇒ Object

Make an API request, including rate limit handling.



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/discordrb/api.rb', line 93

def request(key, major_parameter, type, *attributes)
  # Add a custom user agent
  attributes.last[:user_agent] = user_agent if attributes.last.is_a? Hash

  # The most recent Discord rate limit requirements require the support of major parameters, where a particular route
  # and major parameter combination (*not* the HTTP method) uniquely identifies a RL bucket.
  key = [key, major_parameter].freeze

  begin
    mutex = @mutexes[key] ||= Mutex.new

    # Lock and unlock, i.e. wait for the mutex to unlock and don't do anything with it afterwards
    mutex_wait(mutex)

    # If the global mutex happens to be locked right now, wait for that as well.
    mutex_wait(@global_mutex) if @global_mutex.locked?

    response = nil
    begin
      response = raw_request(type, attributes)
    rescue RestClient::Exception => e
      response = e.response
      raise e
    rescue Discordrb::Errors::NoPermission => e
      if e.respond_to?(:_rc_response)
        response = e._rc_response
      else
        Discordrb::LOGGER.warn("NoPermission doesn't respond_to? _rc_response!")
      end

      raise e
    ensure
      if response
        handle_preemptive_rl(response.headers, mutex, key) if response.headers[:x_ratelimit_remaining] == '0' && !mutex.locked?
      else
        Discordrb::LOGGER.ratelimit('Response was nil before trying to preemptively rate limit!')
      end
    end
  rescue RestClient::TooManyRequests => e
    # If the 429 is from the global RL, then we have to use the global mutex instead.
    mutex = @global_mutex if e.response.headers[:x_ratelimit_global] == 'true'

    unless mutex.locked?
      response = JSON.parse(e.response)
      wait_seconds = response['retry_after'].to_i / 1000.0
      Discordrb::LOGGER.ratelimit("Locking RL mutex (key: #{key}) for #{wait_seconds} seconds due to Discord rate limiting")
      trace("429 #{key.join(' ')}")

      # Wait the required time synchronized by the mutex (so other incoming requests have to wait) but only do it if
      # the mutex isn't locked already so it will only ever wait once
      sync_wait(wait_seconds, mutex)
    end

    retry
  end

  response
end