module MijDiscord::Core::API

Constants

APIBASE_URL
CDN_URL

Public Class Methods

acknowledge_message(auth, channel_id, message_id) click to toggle source

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

# File lib/mij-discord/core/api.rb, line 110
def acknowledge_message(auth, 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,
    "#{APIBASE_URL}/channels/#{channel_id}/messages/#{message_id}/ack",
    nil,
    Authorization: auth
  )
end
app_icon_url(app_id, icon_id, format = :png) click to toggle source

Make an icon URL from application and icon IDs

# File lib/mij-discord/core/api.rb, line 27
def app_icon_url(app_id, icon_id, format = :png)
  "#{CDN_URL}/app-icons/#{app_id}/#{icon_id}.#{format}"
end
create_oauth_application(auth, name, redirect_uris) click to toggle source

Create an OAuth application

# File lib/mij-discord/core/api.rb, line 71
def create_oauth_application(auth, name, redirect_uris)
  request(
    :oauth2_applications,
    nil,
    :post,
    "#{APIBASE_URL}/oauth2/applications",
    { name: name, redirect_uris: redirect_uris }.to_json,
    Authorization: auth,
    content_type: :json
  )
end
emoji_icon_url(emoji_id, format = :png) click to toggle source

Make an emoji icon URL from emoji ID

# File lib/mij-discord/core/api.rb, line 42
def emoji_icon_url(emoji_id, format = :png)
  "#{CDN_URL}/emojis/#{emoji_id}.#{format}"
end
gateway(auth) click to toggle source

Get the gateway to be used

# File lib/mij-discord/core/api.rb, line 122
def gateway(auth)
  request(
    :gateway,
    nil,
    :get,
    "#{APIBASE_URL}/gateway",
    Authorization: auth,
  )
end
icon_url(server_id, icon_id, format = :png) click to toggle source

Make an icon URL from server and icon IDs

# File lib/mij-discord/core/api.rb, line 22
def icon_url(server_id, icon_id, format = :png)
  "#{CDN_URL}/icons/#{server_id}/#{icon_id}.#{format}"
end
login(email, password) click to toggle source

Login to the server

# File lib/mij-discord/core/api.rb, line 47
def login(email, password)
  request(
    :auth_login,
    nil,
    :post,
    "#{APIBASE_URL}/auth/login",
    email: email,
    password: password
  )
end
logout(auth) click to toggle source

Logout from the server

# File lib/mij-discord/core/api.rb, line 59
def logout(auth)
  request(
    :auth_logout,
    nil,
    :post,
    "#{APIBASE_URL}/auth/logout",
    nil,
    Authorization: auth
  )
end
oauth_application(auth) click to toggle source

Get the bot's OAuth application's information

# File lib/mij-discord/core/api.rb, line 97
def oauth_application(auth)
  request(
    :oauth2_applications_me,
    nil,
    :get,
    "#{APIBASE_URL}/oauth2/applications/@me",
    Authorization: auth
  )
end
raw_request(type, *attributes) click to toggle source
# File lib/mij-discord/core/api.rb, line 157
def raw_request(type, *attributes)
  RestClient.send(type, *attributes)
rescue RestClient::RequestFailed => e
  # Holy fuck, Discord…
  if (klazz = MijDiscord::Errors::HTTP_ERRORS[e.http_code])
    data = JSON.parse(e.response)
    if data['message'] || data['code']
      raise klazz.new(data['code'], data['message'], e.response)
    elsif (error = (data['content'] || data['embed']).join)
      if MijDiscord::Errors::MessageTooLong.match_pattern?(error)
        raise MijDiscord::Errors::MessageTooLong.new(error, e.response)
      end
    end
  end
  raise
rescue RestClient::BadGateway
  MijDiscord::LOGGER.warn('HTTP') { 'Received 502 Bad Gateway during API request' }
  retry
end
request(key, major_param, type, *attributes) click to toggle source
# File lib/mij-discord/core/api.rb, line 177
def request(key, major_param, type, *attributes)
  ratelimit_delta, response = nil, nil

  if (params = attributes.last).is_a?(Hash)
    params[:user_agent] = user_agent(params[:Authorization])
    ratelimit_delta = params.delete(:header_bypass_delay)
  end

  key = [key, major_param].freeze
  key_mutex = (@rate_limit_mutex[key] ||= Mutex.new)
  global_mutex = @rate_limit_mutex[:global]

  begin
    mutex_wait(key_mutex)
    mutex_wait(global_mutex) if global_mutex.locked?

    response = raw_request(type, *attributes)
  rescue RestClient::TooManyRequests => e
    response = e.response

    is_global = response.headers[:x_ratelimit_global]
    mutex = is_global == 'true' ? global_mutex : key_mutex

    unless mutex.locked?
      response = JSON.parse(e.response)
      retry_after = response['retry_after'].to_i / 1000.0

      MijDiscord::LOGGER.info('HTTP') { "Hit Discord rate limit on <#{key}>, waiting for #{retry_after} seconds" }
      sync_wait(retry_after, mutex)
    end

    retry
  rescue RestClient::Exception => e
    response = e.response
    raise
  ensure
    headers = response&.headers
    if headers && headers[:x_ratelimit_remaining] == '0' && !key_mutex.locked?
      unless ratelimit_delta
        now = Time.rfc2822(headers[:date])
        reset = Time.at(headers[:x_ratelimit_reset].to_i)
        ratelimit_delta = reset - now
      end

      sync_wait(ratelimit_delta, key_mutex)
    end
  end

  response
end
splash_url(server_id, splash_id) click to toggle source

Make a splash URL from server and splash IDs

# File lib/mij-discord/core/api.rb, line 37
def splash_url(server_id, splash_id)
  "#{CDN_URL}{/splashes/#{server_id}/#{splash_id}.jpg"
end
update_oauth_application(auth, name, redirect_uris, description = '', icon = nil) click to toggle source

Change an OAuth application's properties

# File lib/mij-discord/core/api.rb, line 84
def update_oauth_application(auth, name, redirect_uris, description = '', icon = nil)
  request(
    :oauth2_applications,
    nil,
    :put,
    "#{APIBASE_URL}/oauth2/applications",
    { name: name, redirect_uris: redirect_uris, description: description, icon: icon }.to_json,
    Authorization: auth,
    content_type: :json
  )
end
user_agent(auth) click to toggle source
# File lib/mij-discord/core/api.rb, line 9
def user_agent(auth)
  case auth&.type
    when :bot
      bot_name = auth.name || 'generic'
      ua_base = "DiscordBot (https://github.com/Mijyuoon/mij-discord, v#{MijDiscord::VERSION})"
      "#{ua_base} mij-discord/#{MijDiscord::VERSION} #{bot_name}"

    when :user
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:54.0) Gecko/20100101 Firefox/54.0.1'
  end
end
validate_token(auth) click to toggle source

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

# File lib/mij-discord/core/api.rb, line 133
def validate_token(auth)
  request(
    :auth_login,
    nil,
    :post,
    "#{APIBASE_URL}/auth/login",
    {}.to_json,
    Authorization: auth,
    content_type: :json
  )
end
voice_regions(auth) click to toggle source

Get a list of available voice regions

# File lib/mij-discord/core/api.rb, line 146
def voice_regions(auth)
  request(
    :voice_regions,
    nil,
    :get,
    "#{APIBASE_URL}/voice/regions",
    Authorization: auth,
    content_type: :json
  )
end
widget_url(server_id, style = 'shield') click to toggle source

Make a widget picture URL from server ID

# File lib/mij-discord/core/api.rb, line 32
def widget_url(server_id, style = 'shield')
  "#{APIBASE_URL}/guilds/#{server_id}/widget.png?style=#{style}"
end

Private Class Methods

mutex_wait(mutex) click to toggle source
# File lib/mij-discord/core/api.rb, line 234
def mutex_wait(mutex)
  mutex.lock
  mutex.unlock
end
sync_wait(time, mutex) click to toggle source
# File lib/mij-discord/core/api.rb, line 230
def sync_wait(time, mutex)
  mutex.synchronize { sleep(time) }
end