Module: Discordrb::API

Defined in:
lib/discordrb/api.rb

Overview

List of methods representing endpoints in Discord's API

Defined Under Namespace

Modules: Channel, Invite, Server, User

Constant Summary collapse

APIBASE =

The base URL of the Discord REST API.

'https://discordapp.com/api/v6'.freeze

Class Method Summary collapse

Class Method Details

.acknowledge_message(token, channel_id, message_id) ⇒ Object

Acknowledge that a message has been received The last acknowledged message will be sent in the ready packet, so this is an easy way to catch up on messages



214
215
216
217
218
219
220
221
222
223
# File 'lib/discordrb/api.rb', line 214

def acknowledge_message(token, channel_id, message_id)
  request(
    :channels_cid_messages_mid_ack,
    nil, # This endpoint is unavailable for bot accounts and thus isn't subject to its rate limit requirements.
    :post,
    "#{api_base}/channels/#{channel_id}/messages/#{message_id}/ack",
    nil,
    Authorization: token
  )
end

.api_baseString

Returns the currently used API base URL.

Returns:

  • (String)

    the currently used API base URL.



17
18
19
# File 'lib/discordrb/api.rb', line 17

def api_base
  @api_base || APIBASE
end

.api_base=(value) ⇒ Object

Sets the API base URL to something.



22
23
24
# File 'lib/discordrb/api.rb', line 22

def api_base=(value)
  @api_base = value
end

.app_icon_url(app_id, icon_id) ⇒ Object

Make an icon URL from application and icon IDs



131
132
133
# File 'lib/discordrb/api.rb', line 131

def app_icon_url(app_id, icon_id)
  "https://cdn.discordapp.com/app-icons/#{app_id}/#{icon_id}.jpg"
end

.bot_nameString

Returns the bot name, previously specified using #bot_name=.

Returns:

  • (String)

    the bot name, previously specified using #bot_name=.



27
28
29
# File 'lib/discordrb/api.rb', line 27

def bot_name
  @bot_name
end

.bot_name=(value) ⇒ Object

Sets the bot name to something.



32
33
34
# File 'lib/discordrb/api.rb', line 32

def bot_name=(value)
  @bot_name = value
end

.create_oauth_application(token, name, redirect_uris) ⇒ Object

Create an OAuth application



175
176
177
178
179
180
181
182
183
184
185
# File 'lib/discordrb/api.rb', line 175

def create_oauth_application(token, name, redirect_uris)
  request(
    :oauth2_applications,
    nil,
    :post,
    "#{api_base}/oauth2/applications",
    { name: name, redirect_uris: redirect_uris }.to_json,
    Authorization: token,
    content_type: :json
  )
end

.emoji_icon_url(emoji_id) ⇒ Object

Make an emoji icon URL from emoji ID



146
147
148
# File 'lib/discordrb/api.rb', line 146

def emoji_icon_url(emoji_id)
  "https://cdn.discordapp.com/emojis/#{emoji_id}.png"
end

.gateway(token) ⇒ Object

Get the gateway to be used



226
227
228
229
230
231
232
233
234
# File 'lib/discordrb/api.rb', line 226

def gateway(token)
  request(
    :gateway,
    nil,
    :get,
    "#{api_base}/gateway",
    Authorization: token
  )
end

.icon_url(server_id, icon_id) ⇒ Object

Make an icon URL from server and icon IDs



126
127
128
# File 'lib/discordrb/api.rb', line 126

def icon_url(server_id, icon_id)
  "#{api_base}/guilds/#{server_id}/icons/#{icon_id}.jpg"
end

.login(email, password) ⇒ Object

Login to the server



151
152
153
154
155
156
157
158
159
160
# File 'lib/discordrb/api.rb', line 151

def (email, password)
  request(
    :auth_login,
    nil,
    :post,
    "#{api_base}/auth/login",
    email: email,
    password: password
  )
end

.logout(token) ⇒ Object

Logout from the server



163
164
165
166
167
168
169
170
171
172
# File 'lib/discordrb/api.rb', line 163

def logout(token)
  request(
    :auth_logout,
    nil,
    :post,
    "#{api_base}/auth/logout",
    nil,
    Authorization: token
  )
end

.mutex_wait(mutex) ⇒ Object

Wait for a specified mutex to unlock and do nothing with it afterwards.



57
58
59
60
# File 'lib/discordrb/api.rb', line 57

def mutex_wait(mutex)
  mutex.lock
  mutex.unlock
end

.oauth_application(token) ⇒ Object

Get the bot's OAuth application's information



201
202
203
204
205
206
207
208
209
# File 'lib/discordrb/api.rb', line 201

def oauth_application(token)
  request(
    :oauth2_applications_me,
    nil,
    :get,
    "#{api_base}/oauth2/applications/@me",
    Authorization: token
  )
end

.raw_request(type, attributes) ⇒ Object

Performs a RestClient request.

Parameters:

  • type (Symbol)

    The type of HTTP request to use.

  • attributes (Array)

    The attributes for the request.



65
66
67
68
69
70
71
72
# File 'lib/discordrb/api.rb', line 65

def raw_request(type, attributes)
  RestClient.send(type, *attributes)
rescue RestClient::Forbidden
  raise Discordrb::Errors::NoPermission, "The bot doesn't have the required permission to do this!"
rescue RestClient::BadGateway
  Discordrb::LOGGER.warn('Got a 502 while sending a request! Not a big deal, retrying the request')
  retry
end

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

Make an API request, including rate limit handling.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
# File 'lib/discordrb/api.rb', line 75

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 = raw_request(type, attributes)

    if response.headers[:x_ratelimit_remaining] == '0' && !mutex.locked?
      Discordrb::LOGGER.debug "RL bucket depletion detected! Date: #{response.headers[:date]} Reset: #{response.headers[:x_ratelimit_reset]}"

      now = Time.rfc2822(response.headers[:date])
      reset = Time.at(response.headers[:x_ratelimit_reset].to_i)

      delta = reset - now

      Discordrb::LOGGER.warn("Locking RL mutex (key: #{key}) for #{delta} seconds preemptively")
      sync_wait(delta, mutex)
    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.warn("Locking RL mutex (key: #{key}) for #{wait_seconds} seconds due to Discord rate limiting")

      # 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

.reset_mutexesObject

Resets all rate limit mutexes



46
47
48
49
# File 'lib/discordrb/api.rb', line 46

def reset_mutexes
  @mutexes = {}
  @global_mutex = Mutex.new
end

.splash_url(server_id, splash_id) ⇒ Object

Make a splash URL from server and splash IDs



141
142
143
# File 'lib/discordrb/api.rb', line 141

def splash_url(server_id, splash_id)
  "https://cdn.discordapp.com/splashes/#{server_id}/#{splash_id}.jpg"
end

.sync_wait(time, mutex) ⇒ Object

Wait a specified amount of time synchronised with the specified mutex.



52
53
54
# File 'lib/discordrb/api.rb', line 52

def sync_wait(time, mutex)
  mutex.synchronize { sleep time }
end

.update_oauth_application(token, name, redirect_uris, description = '', icon = nil) ⇒ Object

Change an OAuth application's properties



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/discordrb/api.rb', line 188

def update_oauth_application(token, name, redirect_uris, description = '', icon = nil)
  request(
    :oauth2_applications,
    nil,
    :put,
    "#{api_base}/oauth2/applications",
    { name: name, redirect_uris: redirect_uris, description: description, icon: icon }.to_json,
    Authorization: token,
    content_type: :json
  )
end

.user_agentObject

Generate a user agent identifying this requester as discordrb.



37
38
39
40
41
42
43
# File 'lib/discordrb/api.rb', line 37

def user_agent
  # This particular string is required by the Discord devs.
  required = "DiscordBot (https://github.com/meew0/discordrb, v#{Discordrb::VERSION})"
  @bot_name ||= ''

  "rest-client/#{RestClient::VERSION} #{RUBY_ENGINE}/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL} discordrb/#{Discordrb::VERSION} #{required} #{@bot_name}"
end

.validate_token(token) ⇒ Object

Validate a token (this request will fail if the token is invalid)



237
238
239
240
241
242
243
244
245
246
247
# File 'lib/discordrb/api.rb', line 237

def validate_token(token)
  request(
    :auth_login,
    nil,
    :post,
    "#{api_base}/auth/login",
    {}.to_json,
    Authorization: token,
    content_type: :json
  )
end

.widget_url(server_id, style = 'shield') ⇒ Object

Make a widget picture URL from server ID



136
137
138
# File 'lib/discordrb/api.rb', line 136

def widget_url(server_id, style = 'shield')
  "#{api_base}/guilds/#{server_id}/widget.png?style=#{style}"
end