discordrb 2.1.3 → 3.0.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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3c9854234e0127b9b2bd32b57c4e342e24911dc2
4
- data.tar.gz: 965f0876a53079a4a088f42e7a585262d8aef148
3
+ metadata.gz: eae3c0ee1c9f49e7e8c498ce1db7361a9e812053
4
+ data.tar.gz: ed4937a0c6c8a4c1c084fa684c78e5b8f11ace7b
5
5
  SHA512:
6
- metadata.gz: de1878554378580ab9de645ca98f999b3860c92e29930d46728cea22903b192202b72ace7090a632206cf0b4838d0c764fcbeb611a138d7b3ff6131e9ece54c7
7
- data.tar.gz: e974aed5669570e2ea03cbd85748b96d3b024eb66395d8005747311fd312e85a71906f17fd6936cefc208d1a1889e542936441a0d41b6ca69e093bcf022ae921
6
+ metadata.gz: 6b1fddaa771f734968bc3d0172b8fe87a95a442d9d6ee3ace91ad8e9a182a94c8b1829d333272d01dcad6a8a0cfad676d0c7f1018b34ba9afa66acdca58cf32e
7
+ data.tar.gz: d1fe90193e8abd77d749a5982bb8ba4bb23b88625acd80f80fbdb0a37ab99127af5574cd3284c187258b254dbb863e235d9d85c313ec8ff5e71253a12bca0923
data/.gitignore CHANGED
@@ -8,3 +8,4 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  /.idea/
11
+ .DS_Store
data/CHANGELOG.md CHANGED
@@ -1,5 +1,81 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.0
4
+
5
+ I didn't think there could possibly be a release larger than 2.0.0 was, but here it is! Including the respective release commit, there were 540 commits from 1.8.1 to 2.0.0, but a whopping 734 commits from 2.1.3 to 3.0.0.
6
+
7
+ As with 2.0.0, there are some breaking changes! They are, as always, highlighted in bold.
8
+
9
+ - **The `application_id` parameter has been renamed to `client_id`**. With the changes to how bot applications work, it would just be confusing to have it be called `application_id` any longer. If you try to use `application_id` now, it will raise a descriptive exception; with 3.1.0 that will be removed too (you'll get a less descriptive exception).
10
+ - The gateway implementation has been completely rewritten, for more performance, stability and maintainability. This means that **to call some internal methods like `inject_reconnect`, a `Gateway` instance (available as `Bot#gateway`) now needs to be used.**
11
+ - **User login using email and password has been removed**. Use a user token instead, see also [here](https://github.com/hammerandchisel/discord-api-docs/issues/69#issuecomment-223886862).
12
+ - In addition to the rewrite, the gateway version has also been upgraded to protocol version 6 (the rewrite was for v5). **With this, the way channel types are handled has been changed a bit!** If you've been using the abstraction methods like `Channel#voice?`, you should be fine though. This also includes support for group chats on user accounts, as that was the only real functionality change on v6. ([#211](https://github.com/meew0/discordrb/pull/211), thanks @Daniel-Worrall)
13
+ - **Custom prefix handlers for `CommandBot`s now get the full message object as their parameter rather than only the content**, for even more flexibility.
14
+ - For internal consistency, **the `UnknownGuild` error was renamed to `UnknownServer`**. I doubt this change affects anyone, but if you handle that error specifically in your bot, make sure to change it.
15
+ - **The API module has undergone a refactor**, if you were using any manual API calls you will have to update them to the new format. Specifically, endpoints dealing with channels have been moved to `API::Channel`, ones dealing with users to `API::User` and so on. ([#203](https://github.com/meew0/discordrb/pull/203), thanks @depl0y)
16
+ - **Calling `users` on a text channel will now only return users who have permission to read it** ([#186](https://github.com/meew0/discordrb/issues/186))
17
+ - A variety of new fields have been added to `Message` objects, specifically embeds (`Message#embeds`), when it was last edited (`#edited_timestamp`), whether it uses TTS (`#tts?`), its nonce (`#nonce`), whether it was ever edited (`#edited?`), and whether it mentions everyone (`mention_everyone?`) ([#206](https://github.com/meew0/discordrb/pull/206), thanks @SnazzyPine25)
18
+ - A variety of new functionality has been added to `Server` and `Channel` objects ([#181](https://github.com/meew0/discordrb/pull/181), thanks @SnazzyPine25):
19
+ - Bitrate and user limit can now be read and set for voice channels
20
+ - Server integrations can now be read
21
+ - Server features and verification level can now be read
22
+ - Utility functions to generate widget, widget banner and splash URLs
23
+ - Message pinning is now supported, both reading pin status and pinning existing messages ([#145](https://github.com/meew0/discordrb/issues/145) / [#146](https://github.com/meew0/discordrb/pull/146), thanks @hlaaftana)
24
+ - Support for the new available statuses:
25
+ - `Bot#dnd` to make the bot show up as DnD (red dot)
26
+ - `Bot#invisible` to make the bot show up as offline
27
+ - Setting the bot's status to streaming is now supported ([#128](https://github.com/meew0/discordrb/pull/128) and [#143](https://github.com/meew0/discordrb/pull/143), thanks @SnazzyPine25 and @barkerja)
28
+ - You can now set a message to be sent when a `CommandBot`'s command fails with a `NoPermission` error ([#200](https://github.com/meew0/discordrb/pull/200), thanks @PoVa)
29
+ - There is now an optional field to list the parameters a command can accept ([#201](https://github.com/meew0/discordrb/pull/201), thanks @FormalHellhound)
30
+ - Commands can now have an array of roles set that are required to be able to use it ([#178](https://github.com/meew0/discordrb/pull/178), thanks @PoVa)
31
+ - Methods like `CommandEvent#<<` for quickly responding to an event are now available in `MessageEvent` too ([#154](https://github.com/meew0/discordrb/pull/154), thanks @hlaaftana)
32
+ - Temporary messages, that automatically delete after some time, can now be sent to channels ([#136](https://github.com/meew0/discordrb/issues/136) / [#139](https://github.com/meew0/discordrb/pull/139), thanks @unleashy)
33
+ - Captions can now be sent together with files, and files can be attached to events to be sent on completion ([#130](https://github.com/meew0/discordrb/pull/130), thanks @SnazzyPine25)
34
+ - There is now a `Channel#load_message` method to get a single message by its ID ([#174](https://github.com/meew0/discordrb/pull/174), thanks @z64)
35
+ - `Channel#define_overwrite` can now be used with a `Profile` object, together with some internal changes ([#232](https://github.com/meew0/discordrb/issues/232))
36
+ - There are now endpoint methods to list a server's channels and channel invites ([#197](https://github.com/meew0/discordrb/pull/197))
37
+ - Two methods, `Member#roles=` and `Member#modify_roles` to manipulate a member's roles in a more advanced way have been added ([#223](https://github.com/meew0/discordrb/pull/223), thanks @z64)
38
+ - Role mentionability can now be set using `Role#mentionable=`
39
+ - The current bot's OAuth application can now be read ([#175](https://github.com/meew0/discordrb/pull/175), thanks @SnazzyPine25)
40
+ - You can now mute and deafen other members ([#157](https://github.com/meew0/discordrb/pull/157), thanks @SnazzyPine25)
41
+ - The internal `Logger` now supports writing to a file instead of STDOUT ([#171](https://github.com/meew0/discordrb/issues/171))
42
+ - Building on top of that, you can also write to multiple streams at the same time now, in case you want to have input on both a file and STDOUT, or even more advanced setups. ([#217](https://github.com/meew0/discordrb/pull/217), thanks @PoVa)
43
+ - Roles can now have their permissions bitfield set directly ([#177](https://github.com/meew0/discordrb/issues/177))
44
+ - The `Bot#invite_url` method now supports adding permission bits into the generated URL ([#218](https://github.com/meew0/discordrb/pull/218), thanks @PoVa)
45
+ - A utility method `User#send_file` has been added to directly send a file to a user in PM ([#168](https://github.com/meew0/discordrb/issues/168) / [#172](https://github.com/meew0/discordrb/pull/172), thanks @SnazzyPine25)
46
+ - You can now get the list of members that have a particular role assigned using `Role#members` ([#147](https://github.com/meew0/discordrb/pull/147), thanks @hlaaftana)
47
+ - You can now check whether a `VoiceBot` is playing right now using `#playing?` ([#137](https://github.com/meew0/discordrb/pull/137), thanks @SnazzyPine25)
48
+ - You can now get the channel a `VoiceBot` is playing on ([#138](https://github.com/meew0/discordrb/pull/138), thanks @snapcase)
49
+ - The permissions bit map has been updated for emoji, "Administrator" and nickname changes ([#180](https://github.com/meew0/discordrb/pull/180), thanks @megumisonoda)
50
+ - A method `Bot#connected?` has been added to check whether the bot is currently connected to the gateway.
51
+ - The indescriptive error message that was previously sent when calling methods like `Bot#game=` without an active gateway connection has been replaced with a more descriptive one.
52
+ - The bot's token is now, by default, redacted from any logging output; this can be turned off if desired using the `redact_token` initialization parameter. ([#225](https://github.com/meew0/discordrb/issues/225) / [#231](https://github.com/meew0/discordrb/pull/231), thanks @Daniel-Worrall)
53
+ - The new rate limit headers are now supported. This will have no real impact on any code using discordrb, but it means discordrb is now considered compliant again. See also [here](https://github.com/hammerandchisel/discord-api-docs/issues/108).
54
+ - Rogue presences, i.e. presences without an associated cached member, now print a log message instead of being completely ignored
55
+ - A variety of aliases have been added to existing methods.
56
+ - An example to show off voice sending has been added to the repo, and existing examples have been improved.
57
+ - A large amount of fixes and clarifications have been made to the docs.
58
+
59
+ ### Bugfixes
60
+
61
+ - The almost a year old bug where changing the own user's username would reset its avatar has finally been fixed.
62
+ - The issue where resolving a large server with the owner offline would sometimes cause a stack overflow has been fixed ([#169](https://github.com/meew0/discordrb/issues/169) / [#170](https://github.com/meew0/discordrb/issues/170) / [#191](https://github.com/meew0/discordrb/pull/191), thanks @stoodfarback)
63
+ - Fixed an issue where if a server had an AFK channel set, but that AFK channel couldn't be connected to, resolving the server (and in turn all objects depending on it) would fail. This likely fixes any random `NoPermission` errors you've ever encountered in your log.
64
+ - A message's author will be resolved over the REST API like other objects in case it's not cached yet. This should fix all instances of "Member not cached even thought it should be". ([#210](https://github.com/meew0/discordrb/pull/210), thanks @megumisonoda)
65
+ - Voice state handling has been completely redone, fixing a variety of caching issues. ([#159](https://github.com/meew0/discordrb/issues/159))
66
+ - Getting a voice channel's users no longer does a chunk request ([#142](https://github.com/meew0/discordrb/issues/142))
67
+ - `Channel#define_overwrite` can now be used to define user overwrites, apparently that didn't work at all before
68
+ - Nested command chains where an inner command doesn't exist now no longer crash the command chain handler ([#215](https://github.com/meew0/discordrb/issues/215))
69
+ - Gateway errors should no longer spam the console ([#141](https://github.com/meew0/discordrb/issues/141) / [#148](https://github.com/meew0/discordrb/pull/148), thanks @meew0)
70
+ - Role hoisting (both setting and reading it) should now work properly
71
+ - The `VoiceBot#stop_playing` method should now work more predictably
72
+ - Voice states with a nil channel should no longer crash when accessed ([#183](https://github.com/meew0/discordrb/pull/183), thanks @Apexal)
73
+ - A latent bug in how PM channels were cached is fixed, previously they were cached twice - once by channel ID and once by recipient ID. Now they're only cached by recipient ID.
74
+ - Two problems in how Discord outages are handled are now fixed; the bot should now no longer break when one happens. Specifically, the fixed problems are:
75
+ - `GUILD_DELETE` events for unavailable servers are now ignored
76
+ - Opcode 9 packets which are received while no session currently exists are handled correctly
77
+ - A possible regression in PM channel creation was fixed. ([#227](https://github.com/meew0/discordrb/issues/227) / [#228](https://github.com/meew0/discordrb/pull/228), thanks @heimidal)
78
+
3
79
  ## 2.1.3
4
80
 
5
81
  *Bugfix-only release.*
data/README.md CHANGED
@@ -30,6 +30,8 @@ This section only applies to you if you want to use voice functionality.
30
30
  * A compiled libopus distribution for your system, anywhere the script can find it (on Windows, make sure it's named `opus.dll`)
31
31
  * [FFmpeg](https://www.ffmpeg.org/download.html) installed and in your PATH
32
32
 
33
+ In addition to this, if you're on Windows and want to use voice functionality, your installed Ruby version **needs to be 32 bit**, as otherwise Opus won't work.
34
+
33
35
  ## Installation
34
36
 
35
37
  ### Linux
@@ -40,9 +42,9 @@ On Linux, it should be as simple as running:
40
42
 
41
43
  ### Windows
42
44
 
43
- On Windows, to install discordrb, run this in a shell:
45
+ 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)**:
44
46
 
45
- gem install discordrb
47
+ gem install discordrb --platform=ruby
46
48
 
47
49
  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'!):
48
50
 
data/discordrb.gemspec CHANGED
@@ -31,5 +31,5 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency 'rake', '~> 10.0'
32
32
  spec.add_development_dependency 'yard', '~> 0.8.7.6'
33
33
  spec.add_development_dependency 'rspec', '~> 3.4.0'
34
- spec.add_development_dependency 'rubocop', '0.39.0'
34
+ spec.add_development_dependency 'rubocop', '0.42.0'
35
35
  end
data/examples/commands.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'discordrb'
4
4
 
5
+ # Here we instantiate a `CommandBot` instead of a regular `Bot`, which has the functionality to add commands using the
6
+ # `command` method. We have to set a `prefix` here, which will be the character that triggers command execution.
5
7
  bot = Discordrb::Commands::CommandBot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543, prefix: '!'
6
8
 
7
9
  bot.command :user do |event|
Binary file
Binary file
data/examples/eval.rb CHANGED
@@ -1,16 +1,19 @@
1
- # Gives you the ability to execute code on the fly
1
+ # Eval bots are useful for developers because they give you a way to execute code directly from your Discord channel,
2
+ # e.g. to quickly check something, demonstrate something to other, or something else entirely. Special care must be
3
+ # taken since anyone with access to the command can execute arbitrary code on your system which may potentially be
4
+ # malicious.
2
5
 
3
6
  require 'discordrb'
4
7
 
5
8
  bot = Discordrb::Commands::CommandBot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543, prefix: '!'
6
9
 
7
10
  bot.command(:eval, help_available: false) do |event, *code|
8
- break unless event.user.id == 000000 # Replace number with your ID
11
+ break unless event.user.id == 66237334693085184 # Replace number with your ID
9
12
 
10
13
  begin
11
14
  eval code.join(' ')
12
15
  rescue
13
- "An error occured 😞"
16
+ 'An error occured 😞'
14
17
  end
15
18
  end
16
19
 
data/examples/ping.rb CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  require 'discordrb'
4
4
 
5
+ # This statement creates a bot with the specified token and application ID. After this line, you can add events to the
6
+ # created bot, and eventually run it.
7
+ #
8
+ # If you don't yet have a token and application ID to put in here, you will need to create a bot account here:
9
+ # https://discordapp.com/developers/applications/me
10
+ # If you're wondering about what redirect URIs and RPC origins, you can ignore those for now. If that doesn't satisfy
11
+ # you, look here: https://github.com/meew0/discordrb/wiki/Redirect-URIs-and-RPC-origins
12
+ # After creating the bot, simply copy the token (*not* the OAuth2 secret) and the client ID and put it into the
13
+ # respective places.
5
14
  bot = Discordrb::Bot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543
6
15
 
7
16
  # Here we output the invite URL to the console so the bot account can be invited to the channel. This only has to be
@@ -9,8 +18,12 @@ bot = Discordrb::Bot.new token: 'B0T.T0KEN.here', application_id: 16012345678987
9
18
  puts "This bot's invite URL is #{bot.invite_url}."
10
19
  puts 'Click on it to invite it to your server.'
11
20
 
12
- bot.message(with_text: 'Ping!') do |event|
21
+ # This method call adds an event handler that will be called on any message that exactly contains the string "Ping!".
22
+ # The code inside it will be executed, and a "Pong!" response will be sent to the channel.
23
+ bot.message(content: 'Ping!') do |event|
13
24
  event.respond 'Pong!'
14
25
  end
15
26
 
27
+ # This method call has to be put at the end of your script, it is what makes the bot actually connect to Discord. If you
28
+ # leave it out (try it!) the script will simply stop and the bot will not appear online.
16
29
  bot.run
@@ -1,13 +1,15 @@
1
- # Pinging the bot will also tell you the time it takes the bot to send the message
1
+ # This example is nearly the same as the normal ping example, but rather than simply responding with "Pong!", it also
2
+ # responds with the time it took to send the message.
2
3
 
3
4
  require 'discordrb'
4
5
 
5
- bot = Discordrb::Commands::CommandBot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543, prefix: '!'
6
+ bot = Discordrb::Bot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543
6
7
 
7
- bot.command(:ping) do |event|
8
+ bot.message(content: 'Ping!') do |event|
9
+ # The `respond` method returns a `Message` object, which is stored in a variable `m`. The `edit` method is then called
10
+ # to edit the message with the time difference between when the event was received and after the message was sent.
8
11
  m = event.respond('Pong!')
9
12
  m.edit "Pong! Time taken: #{Time.now - event.timestamp} seconds."
10
- nil
11
13
  end
12
14
 
13
15
  bot.run
data/examples/pm_send.rb CHANGED
@@ -4,7 +4,10 @@ require 'discordrb'
4
4
 
5
5
  bot = Discordrb::Bot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543
6
6
 
7
+ # The `mention` event is called if the bot is *directly mentioned*, i.e. not using a role mention or @everyone/@here.
7
8
  bot.mention do |event|
9
+ # The `pm` method is used to send a private message (also called a DM or direct message) to the user who sent the
10
+ # initial message.
8
11
  event.user.pm('You have mentioned me!')
9
12
  end
10
13
 
data/examples/shutdown.rb CHANGED
@@ -1,11 +1,16 @@
1
- # This script allows you to shutdown the bot on command
1
+ # This bot doesn't do anything except for letting a specifically authorised user shutdown the bot on command.
2
2
 
3
3
  require 'discordrb'
4
4
 
5
5
  bot = Discordrb::Commands::CommandBot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543, prefix: '!'
6
6
 
7
+ # Here we can see the `help_available` property used, which can determine whether a command shows up in the default
8
+ # generated `help` command. It is true by default but it can be set to false to hide internal commands that only
9
+ # specific people can use.
7
10
  bot.command(:exit, help_available: false) do |event|
8
- break unless event.user.id == 000000 # Replace number with your ID
11
+ # This is a check that only allows a user with a specific ID to execute this command. Otherwise, everyone would be
12
+ # able to shut your bot down whenever they wanted.
13
+ break unless event.user.id == 66237334693085184 # Replace number with your ID
9
14
 
10
15
  bot.send_message(event.channel.id, 'Bot is shutting down')
11
16
  exit
@@ -0,0 +1,51 @@
1
+ # discordrb can send music or other audio data to voice channels. This example exists to show that off.
2
+ #
3
+ # To avoid copyright infringement, the example music I will be using is a self-composed piece of highly debatable
4
+ # quality. If you want something better you can replace the files in the data/ directory. Make sure to execute this
5
+ # example from the appropriate place, so that it has access to the files in that directory.
6
+
7
+ require 'discordrb'
8
+
9
+ bot = Discordrb::Commands::CommandBot.new token: 'B0T.T0KEN.here', application_id: 160123456789876543, prefix: '!'
10
+
11
+ bot.command(:connect) do |event|
12
+ # The `voice_channel` method returns the voice channel the user is currently in, or `nil` if the user is not in a
13
+ # voice channel.
14
+ channel = event.user.voice_channel
15
+
16
+ # Here we return from the command unless the channel is not nil (i. e. the user is in a voice channel). The `next`
17
+ # construct can be used to exit a command prematurely, and even send a message while we're at it.
18
+ next "You're not in any voice channel!" unless channel
19
+
20
+ # The `voice_connect` method does everything necessary for the bot to connect to a voice channel. Afterwards the bot
21
+ # will be connected and ready to play stuff back.
22
+ bot.voice_connect(channel)
23
+ "Connected to voice channel: #{channel.name}"
24
+ end
25
+
26
+ # A simple command that plays back an mp3 file.
27
+ bot.command(:play_mp3) do |event|
28
+ # `event.voice` is a helper method that gets the correct voice bot on the server the bot is currently in. Since a
29
+ # bot may be connected to more than one voice channel (never more than one on the same server, though), this is
30
+ # necessary to allow the differentiation of servers.
31
+ #
32
+ # It returns a `VoiceBot` object that methods such as `play_file` can be called on.
33
+ voice_bot = event.voice
34
+ voice_bot.play_file('data/music.mp3')
35
+ end
36
+
37
+ # DCA is a custom audio format developed by a couple people from the Discord API community (including myself, meew0).
38
+ # It represents the audio data exactly as Discord wants it in a format that is very simple to parse, so libraries can
39
+ # very easily add support for it. It has the advantage that absolutely no transcoding has to be done, so it is very
40
+ # light on CPU in comparison to `play_file`.
41
+ #
42
+ # A conversion utility that converts existing audio files to DCA can be found here: https://github.com/nstafie/dca-rs
43
+ bot.command(:play_dca) do |event|
44
+ voice_bot = event.voice
45
+
46
+ # Since the DCA format is non-standard (i.e. ffmpeg doesn't support it), a separate method other than `play_file` has
47
+ # to be used to play DCA files back. `play_dca` fulfills that role.
48
+ voice_bot.play_dca('data/music.dca')
49
+ end
50
+
51
+ bot.run
data/lib/discordrb/api.rb CHANGED
@@ -2,13 +2,14 @@
2
2
 
3
3
  require 'rest-client'
4
4
  require 'json'
5
+ require 'time'
5
6
 
6
7
  require 'discordrb/errors'
7
8
 
8
9
  # List of methods representing endpoints in Discord's API
9
10
  module Discordrb::API
10
11
  # The base URL of the Discord REST API.
11
- APIBASE = 'https://discordapp.com/api'.freeze
12
+ APIBASE = 'https://discordapp.com/api/v6'.freeze
12
13
 
13
14
  module_function
14
15
 
@@ -44,6 +45,18 @@ module Discordrb::API
44
45
  # Resets all rate limit mutexes
45
46
  def reset_mutexes
46
47
  @mutexes = {}
48
+ @global_mutex = Mutex.new
49
+ end
50
+
51
+ # Wait a specified amount of time synchronised with the specified mutex.
52
+ def sync_wait(time, mutex)
53
+ mutex.synchronize { sleep time }
54
+ end
55
+
56
+ # Wait for a specified mutex to unlock and do nothing with it afterwards.
57
+ def mutex_wait(mutex)
58
+ mutex.lock
59
+ mutex.unlock
47
60
  end
48
61
 
49
62
  # Performs a RestClient request.
@@ -58,33 +71,49 @@ module Discordrb::API
58
71
  retry
59
72
  end
60
73
 
61
- # Make an API request. Utility function to implement message queueing
62
- # in the future
63
- def request(key, type, *attributes)
74
+ # Make an API request, including rate limit handling.
75
+ def request(key, major_parameter, type, *attributes)
64
76
  # Add a custom user agent
65
77
  attributes.last[:user_agent] = user_agent if attributes.last.is_a? Hash
66
78
 
79
+ # The most recent Discord rate limit requirements require the support of major parameters, where a particular route
80
+ # and major parameter combination (*not* the HTTP method) uniquely identifies a RL bucket.
81
+ key = [key, major_parameter].freeze
82
+
67
83
  begin
68
- if key
69
- @mutexes[key] = Mutex.new unless @mutexes[key]
84
+ mutex = @mutexes[key] ||= Mutex.new
70
85
 
71
- # Lock and unlock, i. e. wait for the mutex to unlock and don't do anything with it afterwards
72
- @mutexes[key].lock
73
- @mutexes[key].unlock
74
- end
86
+ # Lock and unlock, i. e. wait for the mutex to unlock and don't do anything with it afterwards
87
+ mutex_wait(mutex)
88
+
89
+ # If the global mutex happens to be locked right now, wait for that as well.
90
+ mutex_wait(@global_mutex) if @global_mutex.locked?
75
91
 
76
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)
104
+ end
77
105
  rescue RestClient::TooManyRequests => e
78
- raise "Got an HTTP 429 for an untracked API call! Please report this bug together with the following information: #{type} #{attributes}" unless key
106
+ # If the 429 is from the global RL, then we have to use the global mutex instead.
107
+ mutex = @global_mutex if e.response.headers[:x_ratelimit_global] == 'true'
79
108
 
80
- unless @mutexes[key].locked?
109
+ unless mutex.locked?
81
110
  response = JSON.parse(e.response)
82
111
  wait_seconds = response['retry_after'].to_i / 1000.0
83
112
  Discordrb::LOGGER.warn("Locking RL mutex (key: #{key}) for #{wait_seconds} seconds due to Discord rate limiting")
84
113
 
85
114
  # Wait the required time synchronized by the mutex (so other incoming requests have to wait) but only do it if
86
115
  # the mutex isn't locked already so it will only ever wait once
87
- @mutexes[key].synchronize { sleep wait_seconds }
116
+ sync_wait(wait_seconds, mutex)
88
117
  end
89
118
 
90
119
  retry
@@ -93,85 +122,26 @@ module Discordrb::API
93
122
  response
94
123
  end
95
124
 
96
- # Make an avatar URL from the user and avatar IDs
97
- def avatar_url(user_id, avatar_id)
98
- "#{api_base}/users/#{user_id}/avatars/#{avatar_id}.jpg"
99
- end
100
-
101
125
  # Make an icon URL from server and icon IDs
102
126
  def icon_url(server_id, icon_id)
103
127
  "#{api_base}/guilds/#{server_id}/icons/#{icon_id}.jpg"
104
128
  end
105
129
 
106
- # Ban a user from a server and delete their messages from the last message_days days
107
- def ban_user(token, server_id, user_id, message_days)
108
- request(
109
- __method__,
110
- :put,
111
- "#{api_base}/guilds/#{server_id}/bans/#{user_id}?delete-message-days=#{message_days}",
112
- nil,
113
- Authorization: token
114
- )
115
- end
116
-
117
- # Unban a user from a server
118
- def unban_user(token, server_id, user_id)
119
- request(
120
- __method__,
121
- :delete,
122
- "#{api_base}/guilds/#{server_id}/bans/#{user_id}",
123
- Authorization: token
124
- )
125
- end
126
-
127
- # Kick a user from a server
128
- def kick_user(token, server_id, user_id)
129
- request(
130
- __method__,
131
- :delete,
132
- "#{api_base}/guilds/#{server_id}/members/#{user_id}",
133
- Authorization: token
134
- )
135
- end
136
-
137
- # Move a user to a different voice channel
138
- def move_user(token, server_id, user_id, channel_id)
139
- request(
140
- __method__,
141
- :patch,
142
- "#{api_base}/guilds/#{server_id}/members/#{user_id}",
143
- { channel_id: channel_id }.to_json,
144
- Authorization: token,
145
- content_type: :json
146
- )
147
- end
148
-
149
- # Change a user's nickname on a server
150
- def change_nickname(token, server_id, user_id, nick)
151
- request(
152
- __method__,
153
- :patch,
154
- "#{api_base}/guilds/#{server_id}/members/#{user_id}",
155
- { nick: nick }.to_json,
156
- Authorization: token,
157
- content_type: :json
158
- )
130
+ # 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"
159
133
  end
160
134
 
161
- # Get a server's banned users
162
- def bans(token, server_id)
163
- request(
164
- __method__,
165
- :get,
166
- "#{api_base}/guilds/#{server_id}/bans",
167
- Authorization: token
168
- )
135
+ # Make a widget picture URL from server ID
136
+ def widget_url(server_id, style = 'shield')
137
+ "#{api_base}/guilds/#{server_id}/widget.png?style=#{style}"
169
138
  end
170
139
 
171
140
  # Login to the server
172
141
  def login(email, password)
173
142
  request(
174
- __method__,
143
+ :auth_login,
144
+ nil,
175
145
  :post,
176
146
  "#{api_base}/auth/login",
177
147
  email: email,
@@ -182,7 +152,8 @@ module Discordrb::API
182
152
  # Logout from the server
183
153
  def logout(token)
184
154
  request(
185
- __method__,
155
+ :auth_logout,
156
+ nil,
186
157
  :post,
187
158
  "#{api_base}/auth/logout",
188
159
  nil,
@@ -193,7 +164,8 @@ module Discordrb::API
193
164
  # Create an OAuth application
194
165
  def create_oauth_application(token, name, redirect_uris)
195
166
  request(
196
- __method__,
167
+ :oauth2_applications,
168
+ nil,
197
169
  :post,
198
170
  "#{api_base}/oauth2/applications",
199
171
  { name: name, redirect_uris: redirect_uris }.to_json,
@@ -205,7 +177,8 @@ module Discordrb::API
205
177
  # Change an OAuth application's properties
206
178
  def update_oauth_application(token, name, redirect_uris, description = '', icon = nil)
207
179
  request(
208
- __method__,
180
+ :oauth2_applications,
181
+ nil,
209
182
  :put,
210
183
  "#{api_base}/oauth2/applications",
211
184
  { name: name, redirect_uris: redirect_uris, description: description, icon: icon }.to_json,
@@ -214,237 +187,24 @@ module Discordrb::API
214
187
  )
215
188
  end
216
189
 
217
- # Create a server
218
- def create_server(token, name, region = :london)
219
- request(
220
- __method__,
221
- :post,
222
- "#{api_base}/guilds",
223
- { name: name, region: region.to_s }.to_json,
224
- Authorization: token,
225
- content_type: :json
226
- )
227
- end
228
-
229
- # Update a server
230
- def update_server(token, server_id, name, region, icon, afk_channel_id, afk_timeout)
190
+ # Get the bot's OAuth application's information
191
+ def oauth_application(token)
231
192
  request(
232
- __method__,
233
- :patch,
234
- "#{api_base}/guilds/#{server_id}",
235
- { name: name, region: region, icon: icon, afk_channel_id: afk_channel_id, afk_timeout: afk_timeout }.to_json,
236
- Authorization: token,
237
- content_type: :json
238
- )
239
- end
240
-
241
- # Transfer server ownership
242
- def transfer_ownership(token, server_id, user_id)
243
- request(
244
- __method__,
245
- :patch,
246
- "#{api_base}/guilds/#{server_id}",
247
- { owner_id: user_id }.to_json,
248
- Authorization: token,
249
- content_type: :json
250
- )
251
- end
252
-
253
- # Delete a server
254
- def delete_server(token, server_id)
255
- request(
256
- __method__,
257
- :delete,
258
- "#{api_base}/guilds/#{server_id}",
259
- Authorization: token
260
- )
261
- end
262
-
263
- # Leave a server
264
- def leave_server(token, server_id)
265
- request(
266
- __method__,
267
- :delete,
268
- "#{api_base}/users/@me/guilds/#{server_id}",
269
- Authorization: token
270
- )
271
- end
272
-
273
- # Get a channel's data
274
- def channel(token, channel_id)
275
- request(
276
- __method__,
277
- :get,
278
- "#{api_base}/channels/#{channel_id}",
279
- Authorization: token
280
- )
281
- end
282
-
283
- # Get a server's data
284
- def server(token, server_id)
285
- request(
286
- __method__,
287
- :get,
288
- "#{api_base}/guilds/#{server_id}",
289
- Authorization: token
290
- )
291
- end
292
-
293
- # Get a member's data
294
- def member(token, server_id, user_id)
295
- request(
296
- __method__,
297
- :get,
298
- "#{api_base}/guilds/#{server_id}/members/#{user_id}",
299
- Authorization: token
300
- )
301
- end
302
-
303
- # Create a channel
304
- def create_channel(token, server_id, name, type)
305
- request(
306
- __method__,
307
- :post,
308
- "#{api_base}/guilds/#{server_id}/channels",
309
- { name: name, type: type }.to_json,
310
- Authorization: token,
311
- content_type: :json
312
- )
313
- end
314
-
315
- # Update a channel's data
316
- def update_channel(token, channel_id, name, topic, position = 0)
317
- request(
318
- __method__,
319
- :patch,
320
- "#{api_base}/channels/#{channel_id}",
321
- { name: name, position: position, topic: topic }.to_json,
322
- Authorization: token,
323
- content_type: :json
324
- )
325
- end
326
-
327
- # Delete a channel
328
- def delete_channel(token, channel_id)
329
- request(
330
- __method__,
331
- :delete,
332
- "#{api_base}/channels/#{channel_id}",
333
- Authorization: token
334
- )
335
- end
336
-
337
- # Join a server using an invite
338
- def join_server(token, invite_code)
339
- request(
340
- __method__,
341
- :post,
342
- "#{api_base}/invite/#{invite_code}",
193
+ :oauth2_applications_me,
343
194
  nil,
344
- Authorization: token
345
- )
346
- end
347
-
348
- # Resolve an invite
349
- def resolve_invite(token, invite_code)
350
- request(
351
- __method__,
352
195
  :get,
353
- "#{api_base}/invite/#{invite_code}",
196
+ "#{api_base}/oauth2/applications/@me",
354
197
  Authorization: token
355
198
  )
356
199
  end
357
200
 
358
- # Create a private channel
359
- def create_private(token, bot_user_id, user_id)
360
- request(
361
- __method__,
362
- :post,
363
- "#{api_base}/users/#{bot_user_id}/channels",
364
- { recipient_id: user_id }.to_json,
365
- Authorization: token,
366
- content_type: :json
367
- )
368
- rescue RestClient::BadRequest
369
- raise 'Attempted to PM the bot itself!'
370
- end
371
-
372
- # Create an instant invite from a server or a channel id
373
- def create_invite(token, channel_id, max_age = 0, max_uses = 0, temporary = false, xkcd = false)
374
- request(
375
- __method__,
376
- :post,
377
- "#{api_base}/channels/#{channel_id}/invites",
378
- { max_age: max_age, max_uses: max_uses, temporary: temporary, xkcdpass: xkcd }.to_json,
379
- Authorization: token,
380
- content_type: :json
381
- )
382
- end
383
-
384
- # Delete an invite by code
385
- def delete_invite(token, code)
386
- request(
387
- __method__,
388
- :delete,
389
- "#{api_base}/invites/#{code}",
390
- Authorization: token
391
- )
392
- end
393
-
394
- # Send a message to a channel
395
- def send_message(token, channel_id, message, mentions = [], tts = false, guild_id = nil)
396
- request(
397
- "message-#{guild_id}".to_sym,
398
- :post,
399
- "#{api_base}/channels/#{channel_id}/messages",
400
- { content: message, mentions: mentions, tts: tts }.to_json,
401
- Authorization: token,
402
- content_type: :json
403
- )
404
- rescue RestClient::InternalServerError
405
- raise Discordrb::Errors::MessageTooLong, "Message over the character limit (#{message.length} > 2000)"
406
- end
407
-
408
- # Delete a message
409
- def delete_message(token, channel_id, message_id)
410
- request(
411
- __method__,
412
- :delete,
413
- "#{api_base}/channels/#{channel_id}/messages/#{message_id}",
414
- Authorization: token
415
- )
416
- end
417
-
418
- # Delete messages in bulk
419
- def bulk_delete(token, channel_id, messages = [])
420
- request(
421
- __method__,
422
- :post,
423
- "#{api_base}/channels/#{channel_id}/messages/bulk_delete",
424
- { messages: messages }.to_json,
425
- Authorization: token,
426
- content_type: :json
427
- )
428
- end
429
-
430
- # Edit a message
431
- def edit_message(token, channel_id, message_id, message, mentions = [])
432
- request(
433
- :message,
434
- :patch,
435
- "#{api_base}/channels/#{channel_id}/messages/#{message_id}",
436
- { content: message, mentions: mentions }.to_json,
437
- Authorization: token,
438
- content_type: :json
439
- )
440
- end
441
-
442
201
  # Acknowledge that a message has been received
443
202
  # The last acknowledged message will be sent in the ready packet,
444
203
  # so this is an easy way to catch up on messages
445
204
  def acknowledge_message(token, channel_id, message_id)
446
205
  request(
447
- __method__,
206
+ :channels_cid_messages_mid_ack,
207
+ nil, # This endpoint is unavailable for bot accounts and thus isn't subject to its rate limit requirements.
448
208
  :post,
449
209
  "#{api_base}/channels/#{channel_id}/messages/#{message_id}/ack",
450
210
  nil,
@@ -452,93 +212,11 @@ module Discordrb::API
452
212
  )
453
213
  end
454
214
 
455
- # Send a file as a message to a channel
456
- def send_file(token, channel_id, file)
457
- request(
458
- __method__,
459
- :post,
460
- "#{api_base}/channels/#{channel_id}/messages",
461
- { file: file },
462
- Authorization: token
463
- )
464
- end
465
-
466
- # Create a role (parameters such as name and colour will have to be set by update_role afterwards)
467
- def create_role(token, server_id)
468
- request(
469
- __method__,
470
- :post,
471
- "#{api_base}/guilds/#{server_id}/roles",
472
- nil,
473
- Authorization: token
474
- )
475
- end
476
-
477
- # Update a role
478
- # Permissions are the Discord defaults; allowed: invite creation, reading/sending messages,
479
- # sending TTS messages, embedding links, sending files, reading the history, mentioning everybody,
480
- # connecting to voice, speaking and voice activity (push-to-talk isn't mandatory)
481
- def update_role(token, server_id, role_id, name, colour, hoist = false, packed_permissions = 36_953_089)
482
- request(
483
- __method__,
484
- :patch,
485
- "#{api_base}/guilds/#{server_id}/roles/#{role_id}",
486
- { color: colour, name: name, hoist: hoist, permissions: packed_permissions }.to_json,
487
- Authorization: token,
488
- content_type: :json
489
- )
490
- end
491
-
492
- # Delete a role
493
- def delete_role(token, server_id, role_id)
494
- request(
495
- __method__,
496
- :delete,
497
- "#{api_base}/guilds/#{server_id}/roles/#{role_id}",
498
- Authorization: token
499
- )
500
- end
501
-
502
- # Update a user's roles
503
- def update_user_roles(token, server_id, user_id, roles)
504
- request(
505
- __method__,
506
- :patch,
507
- "#{api_base}/guilds/#{server_id}/members/#{user_id}",
508
- { roles: roles }.to_json,
509
- Authorization: token,
510
- content_type: :json
511
- )
512
- end
513
-
514
- # Update a user's permission overrides in a channel
515
- def update_user_overrides(token, channel_id, user_id, allow, deny)
516
- request(
517
- __method__,
518
- :put,
519
- "#{api_base}/channels/#{channel_id}/permissions/#{user_id}",
520
- { type: 'member', id: user_id, allow: allow, deny: deny }.to_json,
521
- Authorization: token,
522
- content_type: :json
523
- )
524
- end
525
-
526
- # Update a role's permission overrides in a channel
527
- def update_role_overrides(token, channel_id, role_id, allow, deny)
528
- request(
529
- __method__,
530
- :put,
531
- "#{api_base}/channels/#{channel_id}/permissions/#{role_id}",
532
- { type: 'role', id: role_id, allow: allow, deny: deny }.to_json,
533
- Authorization: token,
534
- content_type: :json
535
- )
536
- end
537
-
538
215
  # Get the gateway to be used
539
216
  def gateway(token)
540
217
  request(
541
- __method__,
218
+ :gateway,
219
+ nil,
542
220
  :get,
543
221
  "#{api_base}/gateway",
544
222
  Authorization: token
@@ -548,7 +226,8 @@ module Discordrb::API
548
226
  # Validate a token (this request will fail if the token is invalid)
549
227
  def validate_token(token)
550
228
  request(
551
- __method__,
229
+ :auth_login,
230
+ nil,
552
231
  :post,
553
232
  "#{api_base}/auth/login",
554
233
  {}.to_json,
@@ -556,79 +235,6 @@ module Discordrb::API
556
235
  content_type: :json
557
236
  )
558
237
  end
559
-
560
- # Start typing (needs to be resent every 5 seconds to keep up the typing)
561
- def start_typing(token, channel_id)
562
- request(
563
- __method__,
564
- :post,
565
- "#{api_base}/channels/#{channel_id}/typing",
566
- nil,
567
- Authorization: token
568
- )
569
- end
570
-
571
- # Get user data
572
- def user(token, user_id)
573
- request(
574
- __method__,
575
- :get,
576
- "#{api_base}/users/#{user_id}",
577
- Authorization: token
578
- )
579
- end
580
-
581
- # Get profile data
582
- def profile(token)
583
- request(
584
- __method__,
585
- :get,
586
- "#{api_base}/users/@me",
587
- Authorization: token
588
- )
589
- end
590
-
591
- # Get information about a user's connections
592
- def connections(token)
593
- request(
594
- __method__,
595
- :get,
596
- "#{api_base}/users/@me/connections",
597
- Authorization: token
598
- )
599
- end
600
-
601
- # Update user data
602
- def update_user(token, email, password, new_username, avatar, new_password = nil)
603
- request(
604
- __method__,
605
- :patch,
606
- "#{api_base}/users/@me",
607
- { avatar: avatar, email: email, new_password: new_password, password: password, username: new_username }.to_json,
608
- Authorization: token,
609
- content_type: :json
610
- )
611
- end
612
-
613
- # Get the servers a user is connected to
614
- def servers(token)
615
- request(
616
- __method__,
617
- :get,
618
- "#{api_base}/users/@me/guilds",
619
- Authorization: token
620
- )
621
- end
622
-
623
- # Get a list of messages from a channel's history
624
- def channel_log(token, channel_id, amount, before = nil, after = nil)
625
- request(
626
- __method__,
627
- :get,
628
- "#{api_base}/channels/#{channel_id}/messages?limit=#{amount}#{"&before=#{before}" if before}#{"&after=#{after}" if after}",
629
- Authorization: token
630
- )
631
- end
632
238
  end
633
239
 
634
240
  Discordrb::API.reset_mutexes