discordrb 3.2.1 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of discordrb might be problematic. Click here for more details.

Files changed (47) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +4 -0
  3. data/.rubocop.yml +3 -3
  4. data/.travis.yml +28 -3
  5. data/.yardopts +1 -1
  6. data/CHANGELOG.md +555 -144
  7. data/CONTRIBUTING.md +1 -1
  8. data/Gemfile +0 -4
  9. data/README.md +86 -15
  10. data/Rakefile +2 -2
  11. data/bin/travis_build_docs.sh +17 -0
  12. data/discordrb-webhooks.gemspec +2 -1
  13. data/discordrb.gemspec +12 -5
  14. data/lib/discordrb.rb +2 -2
  15. data/lib/discordrb/api.rb +94 -25
  16. data/lib/discordrb/api/channel.rb +53 -17
  17. data/lib/discordrb/api/invite.rb +7 -4
  18. data/lib/discordrb/api/server.rb +173 -36
  19. data/lib/discordrb/api/user.rb +18 -4
  20. data/lib/discordrb/api/webhook.rb +83 -0
  21. data/lib/discordrb/await.rb +1 -1
  22. data/lib/discordrb/bot.rb +191 -102
  23. data/lib/discordrb/cache.rb +39 -9
  24. data/lib/discordrb/commands/command_bot.rb +79 -24
  25. data/lib/discordrb/commands/container.rb +16 -2
  26. data/lib/discordrb/commands/parser.rb +46 -7
  27. data/lib/discordrb/commands/rate_limiter.rb +8 -6
  28. data/lib/discordrb/container.rb +51 -7
  29. data/lib/discordrb/data.rb +1729 -286
  30. data/lib/discordrb/errors.rb +34 -1
  31. data/lib/discordrb/events/generic.rb +1 -1
  32. data/lib/discordrb/events/guilds.rb +1 -0
  33. data/lib/discordrb/events/message.rb +18 -12
  34. data/lib/discordrb/events/presence.rb +7 -2
  35. data/lib/discordrb/events/reactions.rb +13 -4
  36. data/lib/discordrb/events/roles.rb +7 -6
  37. data/lib/discordrb/events/typing.rb +1 -1
  38. data/lib/discordrb/events/webhooks.rb +61 -0
  39. data/lib/discordrb/gateway.rb +85 -32
  40. data/lib/discordrb/light.rb +1 -1
  41. data/lib/discordrb/logger.rb +8 -7
  42. data/lib/discordrb/permissions.rb +41 -4
  43. data/lib/discordrb/version.rb +1 -1
  44. data/lib/discordrb/voice/encoder.rb +10 -8
  45. data/lib/discordrb/voice/voice_bot.rb +4 -4
  46. data/lib/discordrb/websocket.rb +2 -2
  47. metadata +59 -14
@@ -10,4 +10,4 @@ For PRs, please make sure that you tested your code before every push; it's a li
10
10
  to a PR because of small avoidable oversights. (Huge bonus points if you're adding specs for your code! This project
11
11
  has very few specs in places where it should have more so every added spec is very much appreciated.)
12
12
 
13
- If you have any questions at all, don't be afraid to ask in the [discordrb channel on Discord](https://discord.gg/0SBTUU1wZTWfFQL2).
13
+ If you have any questions at all, don't be afraid to ask in the [discordrb channel on Discord](https://discord.gg/cyK3Hjm).
data/Gemfile CHANGED
@@ -1,9 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Test coverage reporting for CodeClimate
4
- gem 'codeclimate-test-reporter', group: :test, require: nil
5
- gem 'simplecov', group: :test
6
-
7
3
  # Specify your gem's dependencies in discordrb.gemspec
8
4
  gemspec name: 'discordrb'
9
5
  gemspec name: 'discordrb-webhooks', development_group: 'webhooks'
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  [![Gem](https://img.shields.io/gem/v/discordrb.svg)](https://rubygems.org/gems/discordrb)
2
2
  [![Gem](https://img.shields.io/gem/dt/discordrb.svg)](https://rubygems.org/gems/discordrb)
3
3
  [![Build Status](https://travis-ci.org/meew0/discordrb.svg?branch=master)](https://travis-ci.org/meew0/discordrb)
4
- [![Inline docs](http://inch-ci.org/github/meew0/discordrb.svg?branch=master&style=shields)](http://inch-ci.org/github/meew0/discordrb)
4
+ [![Inline docs](https://inch-ci.org/github/meew0/discordrb.svg?branch=master&style=shields)](https://inch-ci.org/github/meew0/discordrb)
5
5
  [![Code Climate](https://codeclimate.com/github/meew0/discordrb/badges/gpa.svg)](https://codeclimate.com/github/meew0/discordrb)
6
6
  [![Test Coverage](https://codeclimate.com/github/meew0/discordrb/badges/coverage.svg)](https://codeclimate.com/github/meew0/discordrb/coverage)
7
- [![Join Discord](https://img.shields.io/badge/discord-join-7289DA.svg)](https://discord.gg/0SBTUU1wZTWfFQL2)
7
+ [![Join Discord](https://img.shields.io/badge/discord-join-7289DA.svg)](https://discord.gg/cyK3Hjm)
8
8
  # discordrb
9
9
 
10
10
  An implementation of the [Discord](https://discordapp.com/) API using Ruby.
@@ -14,16 +14,19 @@ An implementation of the [Discord](https://discordapp.com/) API using Ruby.
14
14
  * [Dependencies](https://github.com/meew0/discordrb#dependencies)
15
15
  * [Installation](https://github.com/meew0/discordrb#installation)
16
16
  * [Usage](https://github.com/meew0/discordrb#usage)
17
+ * [Webhooks Client](https://github.com/meew0/discordrb#webhooks-client)
17
18
  * [Support](https://github.com/meew0/discordrb#support)
18
19
  * [Development](https://github.com/meew0/discordrb#development), [Contributing](https://github.com/meew0/discordrb#contributing)
19
20
  * [License](https://github.com/meew0/discordrb#license)
20
21
 
21
- See also: [Documentation](http://www.rubydoc.info/gems/discordrb), [Tutorials](https://github.com/meew0/discordrb/wiki)
22
+ See also: [Documentation](https://www.rubydoc.info/gems/discordrb), [Tutorials](https://github.com/meew0/discordrb/wiki)
22
23
 
23
24
  ## Dependencies
24
25
 
25
- * Ruby 2.1+
26
- * An installed build system for native extensions (on Windows, try the [DevKit](http://rubyinstaller.org/downloads/); installation instructions [here](https://github.com/oneclick/rubyinstaller/wiki/Development-Kit#quick-start) - you only need to do the quick start)
26
+ * Ruby 2.2+
27
+ * An installed build system for native extensions (on Windows for Ruby < 2.4, try the [DevKit](https://rubyinstaller.org/downloads/); installation instructions [here](https://github.com/oneclick/rubyinstaller/wiki/Development-Kit#quick-start) - you only need to do the quick start)
28
+
29
+ > **Note:** RubyInstaller for Ruby versions 2.4+ will install the DevKit as the last step of the installation.
27
30
 
28
31
  ### Voice dependencies
29
32
 
@@ -36,19 +39,35 @@ In addition to this, if you're on Windows and want to use voice functionality, y
36
39
 
37
40
  ## Installation
38
41
 
39
- ### Linux
42
+ ### With Bundler
43
+
44
+ Using [Bundler](https://bundler.io/#getting-started), you can add discordrb to your Gemfile:
45
+
46
+ gem 'discordrb'
47
+
48
+ And then install via `bundle install`.
49
+
50
+ Run the [ping example](https://github.com/meew0/discordrb/blob/master/examples/ping.rb) to verify that the installation works (make sure to replace the token and client ID in there with your bots'!):
51
+
52
+ To run the bot while using bundler:
53
+
54
+ bundle exec ruby ping.rb
55
+
56
+ ### With Gem
40
57
 
41
- On Linux, it should be as simple as running:
58
+ Alternatively, while Bundler is the recommended option, you can also install discordrb without it.
59
+
60
+ #### Linux / macOS
42
61
 
43
62
  gem install discordrb
44
63
 
45
- ### Windows
64
+ #### Windows
46
65
 
47
- On Windows, to install discordrb, run this in a shell **(make sure you have the DevKit installed! See the [Dependencies](https://github.com/meew0/discordrb#dependencies) section)**:
66
+ > **Make sure you have the DevKit installed! See the [Dependencies](https://github.com/meew0/discordrb#dependencies) section)**
48
67
 
49
68
  gem install discordrb --platform=ruby
50
69
 
51
- Run the [ping example](https://github.com/meew0/discordrb/blob/master/examples/ping.rb) to verify that the installation works (make sure to replace the username and password in there with your own or your bots'!):
70
+ To run the bot:
52
71
 
53
72
  ruby ping.rb
54
73
 
@@ -59,7 +78,23 @@ Run the [ping example](https://github.com/meew0/discordrb/blob/master/examples/p
59
78
  ERROR: Error installing discordrb:
60
79
  The 'websocket-driver' native gem requires installed build tools.
61
80
 
62
- You're missing the development kit required to build native extensions. Download the development kit [here](http://rubyinstaller.org/downloads/) (scroll down to "Development Kit", then choose the one for Ruby 2.0 and your system architecture) and extract it somewhere. Open a command prompt in that folder and run:
81
+ You're missing the development kit required to build native extensions.
82
+
83
+ ##### RubyInstaller for ruby 2.4.3-2 and above
84
+
85
+ RubyInstaller after version 2.3.3 now includes the development kit in the installer. If you do not have the development kit and have installed ruby using RubyInstaller, open a command prompt with administrator privileges and run:
86
+
87
+ ridk install
88
+
89
+ Select option 3, and then run
90
+
91
+ ridk enable
92
+
93
+ To enable the changes
94
+
95
+ ##### RubyInstaller for ruby 2.3.3 and below
96
+
97
+ Download the development kit [here](https://rubyinstaller.org/downloads/) (scroll down to "Development Kit", then choose the one for Ruby 2.0 and your system architecture) and extract it somewhere. Open a command prompt in that folder and run:
63
98
 
64
99
  ruby dk.rb init
65
100
  ruby dk.rb install
@@ -67,6 +102,9 @@ You're missing the development kit required to build native extensions. Download
67
102
  Then reinstall discordrb:
68
103
 
69
104
  gem uninstall discordrb
105
+ bundle install
106
+
107
+ # Or, if you didn't use bundler:
70
108
  gem install discordrb
71
109
 
72
110
  **If Ruby complains about `ffi_c` not being able to be found:**
@@ -88,7 +126,7 @@ You can make a simple bot like this:
88
126
  ```ruby
89
127
  require 'discordrb'
90
128
 
91
- bot = Discordrb::Bot.new token: '<token here>', client_id: 168123456789123456
129
+ bot = Discordrb::Bot.new token: '<token here>'
92
130
 
93
131
  bot.message(with_text: 'Ping!') do |event|
94
132
  event.respond 'Pong!'
@@ -99,9 +137,43 @@ bot.run
99
137
 
100
138
  This bot responds to every "Ping!" with a "Pong!".
101
139
 
140
+ See [additional examples here](https://github.com/meew0/discordrb/tree/master/examples).
141
+
142
+ You can find examples of projects that use discordrb by [searching for the discordrb topic on GitHub](https://github.com/topics/discordrb).
143
+
144
+ If you've made an open source project on GitHub that uses discordrb, consider adding the `discordrb` topic to your repo!
145
+
146
+ ## Webhooks Client
147
+
148
+ Also included is a webhooks client, which can be used as a separate gem `discordrb-webhooks`. This special client can be used to form requests to Discord webhook URLs in a high-level manner.
149
+
150
+ - [`discordrb-webhooks` documentation](https://www.rubydoc.info/gems/discordrb-webhooks)
151
+ - [More information about webhooks](https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks)
152
+ - [Embed visualizer tool](https://leovoel.github.io/embed-visualizer/) - Includes a discordrb code generator for forming embeds
153
+
154
+ ### Usage
155
+
156
+ ```ruby
157
+ require 'discordrb/webhooks'
158
+
159
+ WEBHOOK_URL = 'https://discordapp.com/api/webhooks/424070213278105610/yByxDncRvHi02mhKQheviQI2erKkfRRwFcEp0MMBfib1ds6ZHN13xhPZNS2-fJo_ApSw'.freeze
160
+
161
+ client = Discordrb::Webhooks::Client.new(url: WEBHOOK_URL)
162
+ client.execute do |builder|
163
+ builder.content = 'Hello world!'
164
+ builder.add_embed do |embed|
165
+ embed.title = 'Embed title'
166
+ embed.description = 'Embed description'
167
+ embed.timestamp = Time.now
168
+ end
169
+ end
170
+ ```
171
+
172
+ **Note:** The `discordrb` gem relies on `discordrb-webhooks`. If you already have `discordrb` installed, `require 'discordrb/webhooks'` will include all of the `Webhooks` features as well.
173
+
102
174
  ## Support
103
175
 
104
- You can find me (@meew0, ID 66237334693085184) on the unofficial Discord API server - if you have a question, just ask there, I or somebody else will probably answer you: https://discord.gg/0SBTUU1wZTWfFQL2
176
+ You can find me (@meew0, ID 66237334693085184) on the unofficial Discord API server - if you have a question, just ask there, I or somebody else will probably answer you: https://discord.gg/3Trm6FW
105
177
 
106
178
  ## Development
107
179
 
@@ -115,7 +187,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
115
187
 
116
188
  Bug reports and pull requests are welcome on GitHub at https://github.com/meew0/discordrb.
117
189
 
118
-
119
190
  ## License
120
191
 
121
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
192
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -8,8 +8,8 @@ namespace :webhooks do
8
8
  Bundler::GemHelper.install_tasks(name: 'discordrb-webhooks')
9
9
  end
10
10
 
11
- task build: [:'main:build', :'webhooks:build']
12
- task release: [:'main:release', :'webhooks:release']
11
+ task build: %i[main:build webhooks:build]
12
+ task release: %i[main:release webhooks:release]
13
13
 
14
14
  # Make "build" the default task
15
15
  task default: :build
@@ -0,0 +1,17 @@
1
+ #!/bin/bash
2
+ # Script to build docs for travis usage
3
+
4
+ set -e
5
+
6
+ SOURCE_BRANCH=$TRAVIS_BRANCH
7
+
8
+ if [ -n "$TRAVIS_TAG" ]; then
9
+ SOURCE_BRANCH=$TRAVIS_TAG
10
+ fi
11
+
12
+ # Use YARD to build docs
13
+ bundle exec yard
14
+
15
+ # Move to correct folder
16
+ mkdir -p docs/$SOURCE_BRANCH
17
+ mv doc/* docs/$SOURCE_BRANCH/
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'discordrb/webhooks/version'
@@ -19,7 +20,7 @@ Gem::Specification.new do |spec|
19
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
21
  spec.require_paths = ['lib']
21
22
 
22
- spec.add_dependency 'rest-client'
23
+ spec.add_dependency 'rest-client', '>= 2.1.0.rc1'
23
24
 
24
25
  spec.required_ruby_version = '>= 2.1.0'
25
26
  end
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'discordrb/version'
@@ -17,21 +18,27 @@ Gem::Specification.new do |spec|
17
18
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|examples|lib/discordrb/webhooks)/}) }
18
19
  spec.bindir = 'exe'
19
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.metadata = {
22
+ 'changelog_uri' => 'https://github.com/meew0/discordrb/blob/master/CHANGELOG.md'
23
+ }
20
24
  spec.require_paths = ['lib']
21
25
 
22
- spec.add_dependency 'rest-client'
26
+ spec.add_dependency 'rest-client', '>= 2.1.0.rc1'
23
27
  spec.add_dependency 'opus-ruby'
24
28
  spec.add_dependency 'websocket-client-simple', '>= 0.3.0'
25
29
  spec.add_dependency 'rbnacl', '~> 3.4.0' # 24: update
30
+ spec.add_dependency 'ffi', '>= 1.9.24'
26
31
 
27
- spec.add_dependency 'discordrb-webhooks', '~> 3.2.0.1'
32
+ spec.add_dependency 'discordrb-webhooks', '~> 3.3.0'
28
33
 
29
- spec.required_ruby_version = '>= 2.1.0'
34
+ spec.required_ruby_version = '>= 2.2.4'
30
35
 
31
36
  spec.add_development_dependency 'bundler', '~> 1.10'
32
37
  spec.add_development_dependency 'rake', '~> 10.0'
33
- spec.add_development_dependency 'yard', '~> 0.8.7.6'
38
+ spec.add_development_dependency 'yard', '~> 0.9.9'
39
+ spec.add_development_dependency 'redcarpet', '~> 3.4.0' # YARD markdown formatting
34
40
  spec.add_development_dependency 'rspec', '~> 3.4.0'
35
41
  spec.add_development_dependency 'rspec-prof', '~> 0.0.7'
36
- spec.add_development_dependency 'rubocop', '0.45.0'
42
+ spec.add_development_dependency 'rubocop', '0.49.1'
43
+ spec.add_development_dependency 'simplecov', '~> 0.16.0'
37
44
  end
@@ -15,7 +15,7 @@ end
15
15
 
16
16
  # In discordrb, Integer and {String} are monkey-patched to allow for easy resolution of IDs
17
17
  class Integer
18
- # @return [Integer] The Discord ID represented by this integer, i. e. the integer itself
18
+ # @return [Integer] The Discord ID represented by this integer, i.e. the integer itself
19
19
  def resolve_id
20
20
  self
21
21
  end
@@ -23,7 +23,7 @@ end
23
23
 
24
24
  # In discordrb, {Integer} and String are monkey-patched to allow for easy resolution of IDs
25
25
  class String
26
- # @return [Integer] The Discord ID represented by this string, i. e. the string converted to an integer
26
+ # @return [Integer] The Discord ID represented by this string, i.e. the string converted to an integer
27
27
  def resolve_id
28
28
  to_i
29
29
  end
@@ -11,6 +11,9 @@ module Discordrb::API
11
11
  # The base URL of the Discord REST API.
12
12
  APIBASE = 'https://discordapp.com/api/v6'.freeze
13
13
 
14
+ # The URL of Discord's CDN
15
+ CDN_URL = 'https://cdn.discordapp.com'.freeze
16
+
14
17
  module_function
15
18
 
16
19
  # @return [String] the currently used API base URL.
@@ -23,6 +26,11 @@ module Discordrb::API
23
26
  @api_base = value
24
27
  end
25
28
 
29
+ # @return [String] the currently used CDN url
30
+ def cdn_url
31
+ @cdn_url || CDN_URL
32
+ end
33
+
26
34
  # @return [String] the bot name, previously specified using #bot_name=.
27
35
  def bot_name
28
36
  @bot_name
@@ -33,13 +41,20 @@ module Discordrb::API
33
41
  @bot_name = value
34
42
  end
35
43
 
44
+ # Changes the rate limit tracing behaviour. If rate limit tracing is on, a full backtrace will be logged on every RL
45
+ # hit.
46
+ # @param value [true, false] whether or not to enable rate limit tracing
47
+ def trace=(value)
48
+ @trace = value
49
+ end
50
+
36
51
  # Generate a user agent identifying this requester as discordrb.
37
52
  def user_agent
38
53
  # This particular string is required by the Discord devs.
39
54
  required = "DiscordBot (https://github.com/meew0/discordrb, v#{Discordrb::VERSION})"
40
55
  @bot_name ||= ''
41
56
 
42
- "rest-client/#{RestClient::VERSION} #{RUBY_ENGINE}/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL} discordrb/#{Discordrb::VERSION} #{required} #{@bot_name}"
57
+ "#{required} rest-client/#{RestClient::VERSION} #{RUBY_ENGINE}/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL} discordrb/#{Discordrb::VERSION} #{@bot_name}"
43
58
  end
44
59
 
45
60
  # Resets all rate limit mutexes
@@ -64,8 +79,11 @@ module Discordrb::API
64
79
  # @param attributes [Array] The attributes for the request.
65
80
  def raw_request(type, attributes)
66
81
  RestClient.send(type, *attributes)
67
- rescue RestClient::Forbidden
68
- raise Discordrb::Errors::NoPermission, "The bot doesn't have the required permission to do this!"
82
+ rescue RestClient::Forbidden => e
83
+ # HACK: for #request, dynamically inject restclient's response into NoPermission - this allows us to ratelimit
84
+ noprm = Discordrb::Errors::NoPermission.new
85
+ noprm.define_singleton_method(:_rc_response) { e.response }
86
+ raise noprm, "The bot doesn't have the required permission to do this!"
69
87
  rescue RestClient::BadGateway
70
88
  Discordrb::LOGGER.warn('Got a 502 while sending a request! Not a big deal, retrying the request')
71
89
  retry
@@ -83,24 +101,32 @@ module Discordrb::API
83
101
  begin
84
102
  mutex = @mutexes[key] ||= Mutex.new
85
103
 
86
- # Lock and unlock, i. e. wait for the mutex to unlock and don't do anything with it afterwards
104
+ # Lock and unlock, i.e. wait for the mutex to unlock and don't do anything with it afterwards
87
105
  mutex_wait(mutex)
88
106
 
89
107
  # If the global mutex happens to be locked right now, wait for that as well.
90
108
  mutex_wait(@global_mutex) if @global_mutex.locked?
91
109
 
92
- response = raw_request(type, attributes)
93
-
94
- if response.headers[:x_ratelimit_remaining] == '0' && !mutex.locked?
95
- Discordrb::LOGGER.debug "RL bucket depletion detected! Date: #{response.headers[:date]} Reset: #{response.headers[:x_ratelimit_reset]}"
96
-
97
- now = Time.rfc2822(response.headers[:date])
98
- reset = Time.at(response.headers[:x_ratelimit_reset].to_i)
99
-
100
- delta = reset - now
101
-
102
- Discordrb::LOGGER.warn("Locking RL mutex (key: #{key}) for #{delta} seconds preemptively")
103
- sync_wait(delta, mutex)
110
+ response = nil
111
+ begin
112
+ response = raw_request(type, attributes)
113
+ rescue RestClient::Exception => e
114
+ response = e.response
115
+ raise e
116
+ rescue Discordrb::Errors::NoPermission => e
117
+ if e.respond_to?(:_rc_response)
118
+ response = e._rc_response
119
+ else
120
+ Discordrb::LOGGER.warn("NoPermission doesn't respond_to? _rc_response!")
121
+ end
122
+
123
+ raise e
124
+ ensure
125
+ if response
126
+ handle_preemptive_rl(response.headers, mutex, key) if response.headers[:x_ratelimit_remaining] == '0' && !mutex.locked?
127
+ else
128
+ Discordrb::LOGGER.ratelimit('Response was nil before trying to preemptively rate limit!')
129
+ end
104
130
  end
105
131
  rescue RestClient::TooManyRequests => e
106
132
  # If the 429 is from the global RL, then we have to use the global mutex instead.
@@ -109,7 +135,8 @@ module Discordrb::API
109
135
  unless mutex.locked?
110
136
  response = JSON.parse(e.response)
111
137
  wait_seconds = response['retry_after'].to_i / 1000.0
112
- Discordrb::LOGGER.warn("Locking RL mutex (key: #{key}) for #{wait_seconds} seconds due to Discord rate limiting")
138
+ Discordrb::LOGGER.ratelimit("Locking RL mutex (key: #{key}) for #{wait_seconds} seconds due to Discord rate limiting")
139
+ trace("429 #{key.join(' ')}")
113
140
 
114
141
  # Wait the required time synchronized by the mutex (so other incoming requests have to wait) but only do it if
115
142
  # the mutex isn't locked already so it will only ever wait once
@@ -122,14 +149,44 @@ module Discordrb::API
122
149
  response
123
150
  end
124
151
 
152
+ # Handles premeptive ratelimiting by waiting the given mutex by the difference of the Date header to the
153
+ # X-Ratelimit-Reset header, thus making sure we don't get 429'd in any subsequent requests.
154
+ def handle_preemptive_rl(headers, mutex, key)
155
+ Discordrb::LOGGER.ratelimit "RL bucket depletion detected! Date: #{headers[:date]} Reset: #{headers[:x_ratelimit_reset]}"
156
+
157
+ now = Time.rfc2822(headers[:date])
158
+ reset = Time.at(headers[:x_ratelimit_reset].to_i)
159
+
160
+ delta = reset - now
161
+
162
+ Discordrb::LOGGER.warn("Locking RL mutex (key: #{key}) for #{delta} seconds preemptively")
163
+ sync_wait(delta, mutex)
164
+ end
165
+
166
+ # Perform rate limit tracing. All this method does is log the current backtrace to the console with the `:ratelimit`
167
+ # level.
168
+ # @param reason [String] the reason to include with the backtrace.
169
+ def trace(reason)
170
+ unless @trace
171
+ Discordrb::LOGGER.debug("trace was called with reason #{reason}, but tracing is not enabled")
172
+ return
173
+ end
174
+
175
+ Discordrb::LOGGER.ratelimit("Trace (#{reason}):")
176
+
177
+ caller.each do |str|
178
+ Discordrb::LOGGER.ratelimit(' ' + str)
179
+ end
180
+ end
181
+
125
182
  # Make an icon URL from server and icon IDs
126
- def icon_url(server_id, icon_id)
127
- "#{api_base}/guilds/#{server_id}/icons/#{icon_id}.jpg"
183
+ def icon_url(server_id, icon_id, format = 'webp')
184
+ "#{cdn_url}/icons/#{server_id}/#{icon_id}.#{format}"
128
185
  end
129
186
 
130
187
  # Make an icon URL from application and icon IDs
131
- def app_icon_url(app_id, icon_id)
132
- "https://cdn.discordapp.com/app-icons/#{app_id}/#{icon_id}.jpg"
188
+ def app_icon_url(app_id, icon_id, format = 'webp')
189
+ "#{cdn_url}/app-icons/#{app_id}/#{icon_id}.#{format}"
133
190
  end
134
191
 
135
192
  # Make a widget picture URL from server ID
@@ -138,13 +195,13 @@ module Discordrb::API
138
195
  end
139
196
 
140
197
  # Make a splash URL from server and splash IDs
141
- def splash_url(server_id, splash_id)
142
- "https://cdn.discordapp.com/splashes/#{server_id}/#{splash_id}.jpg"
198
+ def splash_url(server_id, splash_id, format = 'webp')
199
+ "#{cdn_url}/splashes/#{server_id}/#{splash_id}.#{format}"
143
200
  end
144
201
 
145
202
  # Make an emoji icon URL from emoji ID
146
- def emoji_icon_url(emoji_id)
147
- "https://cdn.discordapp.com/emojis/#{emoji_id}.png"
203
+ def emoji_icon_url(emoji_id, format = 'webp')
204
+ "#{cdn_url}/emojis/#{emoji_id}.#{format}"
148
205
  end
149
206
 
150
207
  # Login to the server
@@ -245,6 +302,18 @@ module Discordrb::API
245
302
  content_type: :json
246
303
  )
247
304
  end
305
+
306
+ # Get a list of available voice regions
307
+ def voice_regions(token)
308
+ request(
309
+ :voice_regions,
310
+ nil,
311
+ :get,
312
+ "#{api_base}/voice/regions",
313
+ Authorization: token,
314
+ content_type: :json
315
+ )
316
+ end
248
317
  end
249
318
 
250
319
  Discordrb::API.reset_mutexes