discordrb 3.3.0 → 3.4.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +126 -0
  3. data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
  6. data/.github/pull_request_template.md +37 -0
  7. data/.rubocop.yml +34 -37
  8. data/.travis.yml +5 -6
  9. data/CHANGELOG.md +472 -347
  10. data/Gemfile +2 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +61 -79
  13. data/Rakefile +2 -0
  14. data/bin/console +1 -0
  15. data/discordrb-webhooks.gemspec +6 -6
  16. data/discordrb.gemspec +17 -17
  17. data/lib/discordrb.rb +73 -0
  18. data/lib/discordrb/allowed_mentions.rb +36 -0
  19. data/lib/discordrb/api.rb +40 -15
  20. data/lib/discordrb/api/channel.rb +57 -39
  21. data/lib/discordrb/api/invite.rb +3 -3
  22. data/lib/discordrb/api/server.rb +55 -50
  23. data/lib/discordrb/api/user.rb +8 -8
  24. data/lib/discordrb/api/webhook.rb +6 -6
  25. data/lib/discordrb/await.rb +0 -1
  26. data/lib/discordrb/bot.rb +164 -72
  27. data/lib/discordrb/cache.rb +4 -2
  28. data/lib/discordrb/colour_rgb.rb +43 -0
  29. data/lib/discordrb/commands/command_bot.rb +22 -6
  30. data/lib/discordrb/commands/container.rb +20 -23
  31. data/lib/discordrb/commands/parser.rb +18 -18
  32. data/lib/discordrb/commands/rate_limiter.rb +3 -2
  33. data/lib/discordrb/container.rb +77 -17
  34. data/lib/discordrb/data.rb +25 -4180
  35. data/lib/discordrb/data/activity.rb +264 -0
  36. data/lib/discordrb/data/application.rb +50 -0
  37. data/lib/discordrb/data/attachment.rb +56 -0
  38. data/lib/discordrb/data/audit_logs.rb +345 -0
  39. data/lib/discordrb/data/channel.rb +849 -0
  40. data/lib/discordrb/data/embed.rb +251 -0
  41. data/lib/discordrb/data/emoji.rb +82 -0
  42. data/lib/discordrb/data/integration.rb +83 -0
  43. data/lib/discordrb/data/invite.rb +137 -0
  44. data/lib/discordrb/data/member.rb +297 -0
  45. data/lib/discordrb/data/message.rb +334 -0
  46. data/lib/discordrb/data/overwrite.rb +102 -0
  47. data/lib/discordrb/data/profile.rb +91 -0
  48. data/lib/discordrb/data/reaction.rb +33 -0
  49. data/lib/discordrb/data/recipient.rb +34 -0
  50. data/lib/discordrb/data/role.rb +191 -0
  51. data/lib/discordrb/data/server.rb +1002 -0
  52. data/lib/discordrb/data/user.rb +204 -0
  53. data/lib/discordrb/data/voice_region.rb +45 -0
  54. data/lib/discordrb/data/voice_state.rb +41 -0
  55. data/lib/discordrb/data/webhook.rb +145 -0
  56. data/lib/discordrb/errors.rb +2 -1
  57. data/lib/discordrb/events/bans.rb +7 -5
  58. data/lib/discordrb/events/channels.rb +2 -0
  59. data/lib/discordrb/events/guilds.rb +16 -9
  60. data/lib/discordrb/events/invites.rb +125 -0
  61. data/lib/discordrb/events/members.rb +6 -2
  62. data/lib/discordrb/events/message.rb +69 -27
  63. data/lib/discordrb/events/presence.rb +14 -4
  64. data/lib/discordrb/events/raw.rb +1 -3
  65. data/lib/discordrb/events/reactions.rb +49 -3
  66. data/lib/discordrb/events/typing.rb +6 -4
  67. data/lib/discordrb/events/voice_server_update.rb +47 -0
  68. data/lib/discordrb/events/voice_state_update.rb +15 -10
  69. data/lib/discordrb/events/webhooks.rb +9 -6
  70. data/lib/discordrb/gateway.rb +72 -57
  71. data/lib/discordrb/id_object.rb +39 -0
  72. data/lib/discordrb/light/integrations.rb +1 -1
  73. data/lib/discordrb/light/light_bot.rb +1 -1
  74. data/lib/discordrb/logger.rb +4 -4
  75. data/lib/discordrb/paginator.rb +57 -0
  76. data/lib/discordrb/permissions.rb +103 -8
  77. data/lib/discordrb/version.rb +1 -1
  78. data/lib/discordrb/voice/encoder.rb +3 -3
  79. data/lib/discordrb/voice/network.rb +84 -43
  80. data/lib/discordrb/voice/sodium.rb +96 -0
  81. data/lib/discordrb/voice/voice_bot.rb +34 -26
  82. metadata +93 -55
@@ -5,7 +5,7 @@ module Discordrb::API::User
5
5
  module_function
6
6
 
7
7
  # Get user data
8
- # https://discordapp.com/developers/docs/resources/user#get-user
8
+ # https://discord.com/developers/docs/resources/user#get-user
9
9
  def resolve(token, user_id)
10
10
  Discordrb::API.request(
11
11
  :users_uid,
@@ -17,7 +17,7 @@ module Discordrb::API::User
17
17
  end
18
18
 
19
19
  # Get profile data
20
- # https://discordapp.com/developers/docs/resources/user#get-current-user
20
+ # https://discord.com/developers/docs/resources/user#get-current-user
21
21
  def profile(token)
22
22
  Discordrb::API.request(
23
23
  :users_me,
@@ -43,7 +43,7 @@ module Discordrb::API::User
43
43
  end
44
44
 
45
45
  # Update user data
46
- # https://discordapp.com/developers/docs/resources/user#modify-current-user
46
+ # https://discord.com/developers/docs/resources/user#modify-current-user
47
47
  def update_profile(token, email, password, new_username, avatar, new_password = nil)
48
48
  Discordrb::API.request(
49
49
  :users_me,
@@ -57,7 +57,7 @@ module Discordrb::API::User
57
57
  end
58
58
 
59
59
  # Get the servers a user is connected to
60
- # https://discordapp.com/developers/docs/resources/user#get-current-user-guilds
60
+ # https://discord.com/developers/docs/resources/user#get-current-user-guilds
61
61
  def servers(token)
62
62
  Discordrb::API.request(
63
63
  :users_me_guilds,
@@ -69,7 +69,7 @@ module Discordrb::API::User
69
69
  end
70
70
 
71
71
  # Leave a server
72
- # https://discordapp.com/developers/docs/resources/user#leave-guild
72
+ # https://discord.com/developers/docs/resources/user#leave-guild
73
73
  def leave_server(token, server_id)
74
74
  Discordrb::API.request(
75
75
  :users_me_guilds_sid,
@@ -81,7 +81,7 @@ module Discordrb::API::User
81
81
  end
82
82
 
83
83
  # Get the DMs for the current user
84
- # https://discordapp.com/developers/docs/resources/user#get-user-dms
84
+ # https://discord.com/developers/docs/resources/user#get-user-dms
85
85
  def user_dms(token)
86
86
  Discordrb::API.request(
87
87
  :users_me_channels,
@@ -93,7 +93,7 @@ module Discordrb::API::User
93
93
  end
94
94
 
95
95
  # Create a DM to another user
96
- # https://discordapp.com/developers/docs/resources/user#create-dm
96
+ # https://discord.com/developers/docs/resources/user#create-dm
97
97
  def create_pm(token, recipient_id)
98
98
  Discordrb::API.request(
99
99
  :users_me_channels,
@@ -107,7 +107,7 @@ module Discordrb::API::User
107
107
  end
108
108
 
109
109
  # Get information about a user's connections
110
- # https://discordapp.com/developers/docs/resources/user#get-users-connections
110
+ # https://discord.com/developers/docs/resources/user#get-users-connections
111
111
  def connections(token)
112
112
  Discordrb::API.request(
113
113
  :users_me_connections,
@@ -5,7 +5,7 @@ module Discordrb::API::Webhook
5
5
  module_function
6
6
 
7
7
  # Get a webhook
8
- # https://discordapp.com/developers/docs/resources/webhook#get-webhook
8
+ # https://discord.com/developers/docs/resources/webhook#get-webhook
9
9
  def webhook(token, webhook_id)
10
10
  Discordrb::API.request(
11
11
  :webhooks_wid,
@@ -17,7 +17,7 @@ module Discordrb::API::Webhook
17
17
  end
18
18
 
19
19
  # Get a webhook via webhook token
20
- # https://discordapp.com/developers/docs/resources/webhook#get-webhook-with-token
20
+ # https://discord.com/developers/docs/resources/webhook#get-webhook-with-token
21
21
  def token_webhook(webhook_token, webhook_id)
22
22
  Discordrb::API.request(
23
23
  :webhooks_wid,
@@ -28,7 +28,7 @@ module Discordrb::API::Webhook
28
28
  end
29
29
 
30
30
  # Update a webhook
31
- # https://discordapp.com/developers/docs/resources/webhook#modify-webhook
31
+ # https://discord.com/developers/docs/resources/webhook#modify-webhook
32
32
  def update_webhook(token, webhook_id, data, reason = nil)
33
33
  Discordrb::API.request(
34
34
  :webhooks_wid,
@@ -43,7 +43,7 @@ module Discordrb::API::Webhook
43
43
  end
44
44
 
45
45
  # Update a webhook via webhook token
46
- # https://discordapp.com/developers/docs/resources/webhook#modify-webhook-with-token
46
+ # https://discord.com/developers/docs/resources/webhook#modify-webhook-with-token
47
47
  def token_update_webhook(webhook_token, webhook_id, data, reason = nil)
48
48
  Discordrb::API.request(
49
49
  :webhooks_wid,
@@ -57,7 +57,7 @@ module Discordrb::API::Webhook
57
57
  end
58
58
 
59
59
  # Deletes a webhook
60
- # https://discordapp.com/developers/docs/resources/webhook#delete-webhook
60
+ # https://discord.com/developers/docs/resources/webhook#delete-webhook
61
61
  def delete_webhook(token, webhook_id, reason = nil)
62
62
  Discordrb::API.request(
63
63
  :webhooks_wid,
@@ -70,7 +70,7 @@ module Discordrb::API::Webhook
70
70
  end
71
71
 
72
72
  # Deletes a webhook via webhook token
73
- # https://discordapp.com/developers/docs/resources/webhook#delete-webhook-with-token
73
+ # https://discord.com/developers/docs/resources/webhook#delete-webhook-with-token
74
74
  def token_delete_webhook(webhook_token, webhook_id, reason = nil)
75
75
  Discordrb::API.request(
76
76
  :webhooks_wid,
@@ -43,7 +43,6 @@ module Discordrb
43
43
  dummy_handler = EventContainer.handler_class(@type).new(@attributes, @bot)
44
44
  return [nil, nil] unless event.instance_of?(@type) && dummy_handler.matches?(event)
45
45
 
46
- should_delete = nil
47
46
  should_delete = true if (@block && @block.call(event) != false) || !@block
48
47
 
49
48
  [@key, should_delete]
@@ -18,6 +18,7 @@ require 'discordrb/events/bans'
18
18
  require 'discordrb/events/raw'
19
19
  require 'discordrb/events/reactions'
20
20
  require 'discordrb/events/webhooks'
21
+ require 'discordrb/events/invites'
21
22
 
22
23
  require 'discordrb/api'
23
24
  require 'discordrb/api/channel'
@@ -101,12 +102,14 @@ module Discordrb
101
102
  # to Discord's gateway. `:none` will request that no payloads are received compressed (not recommended for
102
103
  # production bots). `:large` will request that large payloads are received compressed. `:stream` will request
103
104
  # that all data be received in a continuous compressed stream.
105
+ # @param intents [:all, Array<Symbol>, nil] Intents that this bot requires. See {Discordrb::INTENTS}. If `nil`, no intents
106
+ # field will be passed.
104
107
  def initialize(
105
- log_mode: :normal,
106
- token: nil, client_id: nil,
107
- type: nil, name: '', fancy_log: false, suppress_ready: false, parse_self: false,
108
- shard_id: nil, num_shards: nil, redact_token: true, ignore_bots: false,
109
- compress_mode: :stream
108
+ log_mode: :normal,
109
+ token: nil, client_id: nil,
110
+ type: nil, name: '', fancy_log: false, suppress_ready: false, parse_self: false,
111
+ shard_id: nil, num_shards: nil, redact_token: true, ignore_bots: false,
112
+ compress_mode: :large, intents: nil
110
113
  )
111
114
  LOGGER.mode = log_mode
112
115
  LOGGER.token = token if redact_token
@@ -125,8 +128,12 @@ module Discordrb
125
128
 
126
129
  @compress_mode = compress_mode
127
130
 
131
+ raise 'Token string is empty or nil' if token.nil? || token.empty?
132
+
133
+ @intents = intents == :all ? INTENTS.values.reduce(&:|) : calculate_intents(intents) if intents
134
+
128
135
  @token = process_token(@type, token)
129
- @gateway = Gateway.new(self, @token, @shard_key, @compress_mode)
136
+ @gateway = Gateway.new(self, @token, @shard_key, @compress_mode, @intents)
130
137
 
131
138
  init_cache
132
139
 
@@ -160,16 +167,13 @@ module Discordrb
160
167
 
161
168
  # @overload emoji(id)
162
169
  # Return an emoji by its ID
163
- # @param id [Integer, #resolve_id] The emoji's ID.
170
+ # @param id [String, Integer] The emoji's ID.
164
171
  # @return [Emoji, nil] the emoji object. `nil` if the emoji was not found.
165
172
  # @overload emoji
166
173
  # The list of emoji the bot can use.
167
174
  # @return [Array<Emoji>] the emoji available.
168
175
  def emoji(id = nil)
169
- gateway_check
170
- unavailable_servers_check
171
-
172
- emoji_hash = @servers.values.map(&:emoji).reduce(&:merge)
176
+ emoji_hash = servers.values.map(&:emoji).reduce(&:merge)
173
177
  if id
174
178
  id = id.resolve_id
175
179
  emoji_hash[id]
@@ -203,6 +207,7 @@ module Discordrb
203
207
  # @return [Application, nil] The bot's application info. Returns `nil` if bot is not a bot account.
204
208
  def bot_application
205
209
  return unless @type == :bot
210
+
206
211
  response = API.oauth_application(token)
207
212
  Application.new(JSON.parse(response), self)
208
213
  end
@@ -225,7 +230,7 @@ module Discordrb
225
230
 
226
231
  # Runs the bot, which logs into Discord and connects the WebSocket. This
227
232
  # prevents all further execution unless it is executed with
228
- # `backround` = `true`.
233
+ # `background` = `true`.
229
234
  # @param background [true, false] If it is `true`, then the bot will run in
230
235
  # another thread to allow further execution. If it is `false`, this method
231
236
  # will block until {#stop} is called. If the bot is run with `true`, make
@@ -254,9 +259,9 @@ module Discordrb
254
259
 
255
260
  # Stops the bot gracefully, disconnecting the websocket without immediately killing the thread. This means that
256
261
  # Discord is immediately aware of the closed connection and makes the bot appear offline instantly.
257
- # @param no_sync [true, false] Whether or not to disable use of synchronize in the close method. This should be true if called from a trap context.
258
- def stop(no_sync = false)
259
- @gateway.stop(no_sync)
262
+ # @note This method no longer takes an argument as of 3.4.0
263
+ def stop(_no_sync = nil)
264
+ @gateway.stop
260
265
  end
261
266
 
262
267
  # @return [true, false] whether or not the bot is currently connected to Discord.
@@ -273,14 +278,14 @@ module Discordrb
273
278
 
274
279
  # Creates an OAuth invite URL that can be used to invite this bot to a particular server.
275
280
  # @param server [Server, nil] The server the bot should be invited to, or nil if a general invite should be created.
276
- # @param permission_bits [Integer, String] Permission bits that should be appended to invite url.
281
+ # @param permission_bits [String, Integer] Permission bits that should be appended to invite url.
277
282
  # @return [String] the OAuth invite URL.
278
283
  def invite_url(server: nil, permission_bits: nil)
279
284
  @client_id ||= bot_application.id
280
285
 
281
286
  server_id_str = server ? "&guild_id=#{server.id}" : ''
282
287
  permission_bits_str = permission_bits ? "&permissions=#{permission_bits}" : ''
283
- "https://discordapp.com/oauth2/authorize?&client_id=#{@client_id}#{server_id_str}#{permission_bits_str}&scope=bot"
288
+ "https://discord.com/oauth2/authorize?&client_id=#{@client_id}#{server_id_str}#{permission_bits_str}&scope=bot"
284
289
  end
285
290
 
286
291
  # @return [Hash<Integer => VoiceBot>] the voice connections this bot currently has, by the server ID to which they are connected.
@@ -299,22 +304,21 @@ module Discordrb
299
304
 
300
305
  server_id = channel.server.id
301
306
  return @voices[server_id] if @voices[server_id]
302
-
303
- nil
304
307
  end
305
308
 
306
309
  # Connects to a voice channel, initializes network connections and returns the {Voice::VoiceBot} over which audio
307
310
  # data can then be sent. After connecting, the bot can also be accessed using {#voice}. If the bot is already
308
311
  # connected to voice, the existing connection will be terminated - you don't have to call
309
312
  # {Discordrb::Voice::VoiceBot#destroy} before calling this method.
310
- # @param chan [Channel, Integer, #resolve_id] The voice channel to connect to.
311
- # @param encrypted [true, false] Whether voice communication should be encrypted using RbNaCl's SecretBox
313
+ # @param chan [Channel, String, Integer] The voice channel, or its ID, to connect to.
314
+ # @param encrypted [true, false] Whether voice communication should be encrypted using
312
315
  # (uses an XSalsa20 stream cipher for encryption and Poly1305 for authentication)
313
316
  # @return [Voice::VoiceBot] the initialized bot over which audio data can then be sent.
314
317
  def voice_connect(chan, encrypted = true)
318
+ raise ArgumentError, 'Unencrypted voice connections are no longer supported.' unless encrypted
319
+
315
320
  chan = channel(chan.resolve_id)
316
321
  server_id = chan.server.id
317
- @should_encrypt_voice = encrypted
318
322
 
319
323
  if @voices[chan.id]
320
324
  debug('Voice bot exists already! Destroying it')
@@ -336,7 +340,7 @@ module Discordrb
336
340
 
337
341
  # Disconnects the client from a specific voice connection given the server ID. Usually it's more convenient to use
338
342
  # {Discordrb::Voice::VoiceBot#destroy} rather than this.
339
- # @param server [Server, Integer, #resolve_id] The server the voice connection is on.
343
+ # @param server [Server, String, Integer] The server, or server ID, the voice connection is on.
340
344
  # @param destroy_vws [true, false] Whether or not the VWS should also be destroyed. If you're calling this method
341
345
  # directly, you should leave it as true.
342
346
  def voice_destroy(server, destroy_vws = true)
@@ -355,31 +359,37 @@ module Discordrb
355
359
  end
356
360
 
357
361
  # Sends a text message to a channel given its ID and the message's content.
358
- # @param channel [Channel, Integer, #resolve_id] The channel to send something to.
362
+ # @param channel [Channel, String, Integer] The channel, or its ID, to send something to.
359
363
  # @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed).
360
364
  # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
361
365
  # @param embed [Hash, Discordrb::Webhooks::Embed, nil] The rich embed to append to this message.
366
+ # @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
367
+ # @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any.
362
368
  # @return [Message] The message that was sent.
363
- def send_message(channel, content, tts = false, embed = nil)
369
+ def send_message(channel, content, tts = false, embed = nil, attachments = nil, allowed_mentions = nil, message_reference = nil)
364
370
  channel = channel.resolve_id
365
371
  debug("Sending message to #{channel} with content '#{content}'")
372
+ allowed_mentions = { parse: [] } if allowed_mentions == false
373
+ message_reference = { message_id: message_reference.id } if message_reference
366
374
 
367
- response = API::Channel.create_message(token, channel, content, tts, embed ? embed.to_hash : nil)
375
+ response = API::Channel.create_message(token, channel, content, tts, embed&.to_hash, nil, attachments, allowed_mentions&.to_hash, message_reference)
368
376
  Message.new(JSON.parse(response), self)
369
377
  end
370
378
 
371
379
  # Sends a text message to a channel given its ID and the message's content,
372
380
  # then deletes it after the specified timeout in seconds.
373
- # @param channel [Channel, Integer, #resolve_id] The channel to send something to.
381
+ # @param channel [Channel, String, Integer] The channel, or its ID, to send something to.
374
382
  # @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed).
375
383
  # @param timeout [Float] The amount of time in seconds after which the message sent will be deleted.
376
384
  # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
377
385
  # @param embed [Hash, Discordrb::Webhooks::Embed, nil] The rich embed to append to this message.
378
- def send_temporary_message(channel, content, timeout, tts = false, embed = nil)
386
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`
387
+ # @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
388
+ def send_temporary_message(channel, content, timeout, tts = false, embed = nil, attachments = nil, allowed_mentions = nil)
379
389
  Thread.new do
380
390
  Thread.current[:discordrb_name] = "#{@current_thread}-temp-msg"
381
391
 
382
- message = send_message(channel, content, tts, embed)
392
+ message = send_message(channel, content, tts, embed, attachments, allowed_mentions)
383
393
  sleep(timeout)
384
394
  message.delete
385
395
  end
@@ -389,13 +399,24 @@ module Discordrb
389
399
 
390
400
  # Sends a file to a channel. If it is an image, it will automatically be embedded.
391
401
  # @note This executes in a blocking way, so if you're sending long files, be wary of delays.
392
- # @param channel [Channel, Integer, #resolve_id] The channel to send something to.
402
+ # @param channel [Channel, String, Integer] The channel, or its ID, to send something to.
393
403
  # @param file [File] The file that should be sent.
394
404
  # @param caption [string] The caption for the file.
395
405
  # @param tts [true, false] Whether or not this file's caption should be sent using Discord text-to-speech.
406
+ # @param filename [String] Overrides the filename of the uploaded file
407
+ # @param spoiler [true, false] Whether or not this file should appear as a spoiler.
396
408
  # @example Send a file from disk
397
409
  # bot.send_file(83281822225530880, File.open('rubytaco.png', 'r'))
398
- def send_file(channel, file, caption: nil, tts: false)
410
+ def send_file(channel, file, caption: nil, tts: false, filename: nil, spoiler: nil)
411
+ if file.respond_to?(:read)
412
+ if spoiler
413
+ filename ||= File.basename(file.path)
414
+ filename = "SPOILER_#{filename}" unless filename.start_with? 'SPOILER_'
415
+ end
416
+ # https://github.com/rest-client/rest-client/blob/v2.0.2/lib/restclient/payload.rb#L160
417
+ file.define_singleton_method(:original_filename) { filename } if filename
418
+ end
419
+
399
420
  channel = channel.resolve_id
400
421
  response = API::Channel.upload_file(token, channel, file, caption: caption, tts: tts)
401
422
  Message.new(JSON.parse(response), self)
@@ -410,14 +431,13 @@ module Discordrb
410
431
  def create_server(name, region = :'eu-central')
411
432
  response = API::Server.create(token, name, region)
412
433
  id = JSON.parse(response)['id'].to_i
413
- sleep 0.1 until @servers[id]
414
- server = @servers[id]
434
+ sleep 0.1 until (server = @servers[id])
415
435
  debug "Successfully created server #{server.id} with name #{server.name}"
416
436
  server
417
437
  end
418
438
 
419
439
  # Creates a new application to do OAuth authorization with. This allows you to use OAuth to authorize users using
420
- # Discord. For information how to use this, see the docs: https://discordapp.com/developers/docs/topics/oauth2
440
+ # Discord. For information how to use this, see the docs: https://discord.com/developers/docs/topics/oauth2
421
441
  # @param name [String] What your application should be called.
422
442
  # @param redirect_uris [Array<String>] URIs that Discord should redirect your users to after authorizing.
423
443
  # @return [Array(String, String)] your applications' client ID and client secret to be used in OAuth authorization.
@@ -436,28 +456,46 @@ module Discordrb
436
456
  API.update_oauth_application(@token, name, redirect_uris, description, icon)
437
457
  end
438
458
 
439
- # Gets the user, channel, role or emoji from a mention of the user, channel, role or emoji.
459
+ # Gets the users, channels, roles and emoji from a string.
460
+ # @param mentions [String] The mentions, which should look like `<@12314873129>`, `<#123456789>`, `<@&123456789>` or `<:name:126328:>`.
461
+ # @param server [Server, nil] The server of the associated mentions. (recommended for role parsing, to speed things up)
462
+ # @return [Array<User, Channel, Role, Emoji>] The array of users, channels, roles and emoji identified by the mentions, or `nil` if none exists.
463
+ def parse_mentions(mentions, server = nil)
464
+ array_to_return = []
465
+ # While possible mentions may be in message
466
+ while mentions.include?('<') && mentions.include?('>')
467
+ # Removing all content before the next possible mention
468
+ mentions = mentions.split('<', 2)[1]
469
+ # Locate the first valid mention enclosed in `<...>`, otherwise advance to the next open `<`
470
+ next unless mentions.split('>', 2).first.length < mentions.split('<', 2).first.length
471
+
472
+ # Store the possible mention value to be validated with RegEx
473
+ mention = mentions.split('>', 2).first
474
+ if /@!?(?<id>\d+)/ =~ mention
475
+ array_to_return << user(id) unless user(id).nil?
476
+ elsif /#(?<id>\d+)/ =~ mention
477
+ array_to_return << channel(id, server) unless channel(id, server).nil?
478
+ elsif /@&(?<id>\d+)/ =~ mention
479
+ if server
480
+ array_to_return << server.role(id) unless server.role(id).nil?
481
+ else
482
+ @servers.each_value do |element|
483
+ array_to_return << element.role(id) unless element.role(id).nil?
484
+ end
485
+ end
486
+ elsif /(?<animated>^a|^${0}):(?<name>\w+):(?<id>\d+)/ =~ mention
487
+ array_to_return << (emoji(id) || Emoji.new({ 'animated' => !animated.nil?, 'name' => name, 'id' => id }, self, nil))
488
+ end
489
+ end
490
+ array_to_return
491
+ end
492
+
493
+ # Gets the user, channel, role or emoji from a string.
440
494
  # @param mention [String] The mention, which should look like `<@12314873129>`, `<#123456789>`, `<@&123456789>` or `<:name:126328:>`.
441
495
  # @param server [Server, nil] The server of the associated mention. (recommended for role parsing, to speed things up)
442
496
  # @return [User, Channel, Role, Emoji] The user, channel, role or emoji identified by the mention, or `nil` if none exists.
443
497
  def parse_mention(mention, server = nil)
444
- # Mention format: <@id>
445
- if /<@!?(?<id>\d+)>/ =~ mention
446
- user(id)
447
- elsif /<#(?<id>\d+)>/ =~ mention
448
- channel(id, server)
449
- elsif /<@&(?<id>\d+)>/ =~ mention
450
- return server.role(id) if server
451
- @servers.values.each do |element|
452
- role = element.role(id)
453
- return role unless role.nil?
454
- end
455
-
456
- # Return nil if no role is found
457
- nil
458
- elsif /<(?<animated>a)?:(?<name>\w+):(?<id>\d+)>/ =~ mention
459
- emoji(id) || Emoji.new({ 'animated' => !animated.nil?, 'name' => name, 'id' => id }, self, nil)
460
- end
498
+ parse_mentions(mention, server).first
461
499
  end
462
500
 
463
501
  # Updates presence status.
@@ -480,7 +518,7 @@ module Discordrb
480
518
  @gateway.send_status_update(status, since, activity_obj, afk)
481
519
 
482
520
  # Update the status in the cache
483
- profile.update_presence('status' => status.to_s, 'game' => activity_obj)
521
+ profile.update_presence('status' => status.to_s, 'activities' => [activity_obj].compact)
484
522
  end
485
523
 
486
524
  # Sets the currently playing game to the specified game.
@@ -489,7 +527,6 @@ module Discordrb
489
527
  def game=(name)
490
528
  gateway_check
491
529
  update_status(@status, name, nil)
492
- name
493
530
  end
494
531
 
495
532
  alias_method :playing=, :game=
@@ -500,7 +537,6 @@ module Discordrb
500
537
  def listening=(name)
501
538
  gateway_check
502
539
  update_status(@status, name, nil, nil, nil, 2)
503
- name
504
540
  end
505
541
 
506
542
  # Sets the current watching status to the specified name.
@@ -509,7 +545,6 @@ module Discordrb
509
545
  def watching=(name)
510
546
  gateway_check
511
547
  update_status(@status, name, nil, nil, nil, 3)
512
- name
513
548
  end
514
549
 
515
550
  # Sets the currently online stream to the specified name and Twitch URL.
@@ -576,6 +611,7 @@ module Discordrb
576
611
  # @deprecated Will be changed to blocking behavior in v4.0. Use {#add_await!} instead.
577
612
  def add_await(key, type, attributes = {}, &block)
578
613
  raise "You can't await an AwaitEvent!" if type == Discordrb::Events::AwaitEvent
614
+
579
615
  await = Await.new(self, key, type, attributes, block)
580
616
  @awaits ||= {}
581
617
  @awaits[key] = await
@@ -583,14 +619,17 @@ module Discordrb
583
619
 
584
620
  # Awaits an event, blocking the current thread until a response is received.
585
621
  # @param type [Class] The event class that should be listened for.
586
- # @option attributes [Numeric] :timeout the amount of time to wait for a response before returning `nil`. Waits forever if omitted.
622
+ # @option attributes [Numeric] :timeout the amount of time (in seconds) to wait for a response before returning `nil`. Waits forever if omitted.
623
+ # @yield Executed when a matching event is received.
624
+ # @yieldparam event [Event] The event object that was triggered.
625
+ # @yieldreturn [true, false] Whether the event matches extra await criteria described by the block
587
626
  # @return [Event, nil] The event object that was triggered, or `nil` if a `timeout` was set and no event was raised in time.
588
627
  # @raise [ArgumentError] if `timeout` is given and is not a positive numeric value
589
628
  def add_await!(type, attributes = {})
590
629
  raise "You can't await an AwaitEvent!" if type == Discordrb::Events::AwaitEvent
591
630
 
592
631
  timeout = attributes[:timeout]
593
- raise ArgumentError, 'Timeout must be a number > 0' if timeout && timeout.is_a?(Numeric) && timeout <= 0
632
+ raise ArgumentError, 'Timeout must be a number > 0' if timeout.is_a?(Numeric) && !timeout.positive?
594
633
 
595
634
  mutex = Mutex.new
596
635
  cv = ConditionVariable.new
@@ -598,7 +637,12 @@ module Discordrb
598
637
  block = lambda do |event|
599
638
  mutex.synchronize do
600
639
  response = event
601
- cv.signal
640
+ if block_given?
641
+ result = yield(event)
642
+ cv.signal if result.is_a?(TrueClass)
643
+ else
644
+ cv.signal
645
+ end
602
646
  end
603
647
  end
604
648
 
@@ -615,25 +659,26 @@ module Discordrb
615
659
 
616
660
  remove_handler(handler)
617
661
  raise 'ConditionVariable was signaled without returning an event!' if response.nil? && timeout.nil?
662
+
618
663
  response
619
664
  end
620
665
 
621
666
  # Add a user to the list of ignored users. Those users will be ignored in message events at event processing level.
622
667
  # @note Ignoring a user only prevents any message events (including mentions, commands etc.) from them! Typing and
623
668
  # presence and any other events will still be received.
624
- # @param user [User, Integer, #resolve_id] The user, or its ID, to be ignored.
669
+ # @param user [User, String, Integer] The user, or its ID, to be ignored.
625
670
  def ignore_user(user)
626
671
  @ignored_ids << user.resolve_id
627
672
  end
628
673
 
629
674
  # Remove a user from the ignore list.
630
- # @param user [User, Integer, #resolve_id] The user, or its ID, to be unignored.
675
+ # @param user [User, String, Integer] The user, or its ID, to be unignored.
631
676
  def unignore_user(user)
632
677
  @ignored_ids.delete(user.resolve_id)
633
678
  end
634
679
 
635
680
  # Checks whether a user is being ignored.
636
- # @param user [User, Integer, #resolve_id] The user, or its ID, to check.
681
+ # @param user [User, String, Integer] The user, or its ID, to check.
637
682
  # @return [true, false] whether or not the user is ignored.
638
683
  def ignored?(user)
639
684
  @ignored_ids.include?(user.resolve_id)
@@ -677,7 +722,8 @@ module Discordrb
677
722
  # e.g. due to a Discord outage or because the servers are large and taking a while to load.
678
723
  def unavailable_servers_check
679
724
  # Return unless there are servers that are unavailable.
680
- return unless @unavailable_servers && @unavailable_servers > 0
725
+ return unless @unavailable_servers&.positive?
726
+
681
727
  LOGGER.warn("#{@unavailable_servers} servers haven't been cached yet.")
682
728
  LOGGER.warn('Servers may be unavailable due to an outage, or your bot is on very large servers that are taking a while to load.')
683
729
  end
@@ -737,10 +783,21 @@ module Discordrb
737
783
 
738
784
  user_id = data['user_id'].to_i
739
785
  old_voice_state = server.voice_states[user_id]
740
- old_channel_id = old_voice_state.voice_channel.id if old_voice_state
786
+ old_channel_id = old_voice_state.voice_channel&.id if old_voice_state
741
787
 
742
788
  server.update_voice_state(data)
743
789
 
790
+ existing_voice = @voices[server_id]
791
+ if user_id == @profile.id && existing_voice
792
+ new_channel_id = data['channel_id']
793
+ if new_channel_id
794
+ new_channel = channel(new_channel_id)
795
+ existing_voice.channel = new_channel
796
+ else
797
+ voice_destroy(server_id)
798
+ end
799
+ end
800
+
744
801
  old_channel_id
745
802
  end
746
803
 
@@ -751,6 +808,7 @@ module Discordrb
751
808
 
752
809
  debug("Voice server update received! chan: #{channel.inspect}")
753
810
  return unless channel
811
+
754
812
  @should_connect_to_voice.delete(server_id)
755
813
  debug('Updating voice server!')
756
814
 
@@ -763,7 +821,7 @@ module Discordrb
763
821
  end
764
822
 
765
823
  debug('Got data, now creating the bot.')
766
- @voices[server_id] = Discordrb::Voice::VoiceBot.new(channel, self, token, @session_id, endpoint, @should_encrypt_voice)
824
+ @voices[server_id] = Discordrb::Voice::VoiceBot.new(channel, self, token, @session_id, endpoint)
767
825
  end
768
826
 
769
827
  # Internal handler for CHANNEL_CREATE
@@ -787,6 +845,7 @@ module Discordrb
787
845
  channel = Channel.new(data, self)
788
846
  old_channel = @channels[channel.id]
789
847
  return unless old_channel
848
+
790
849
  old_channel.update_from(channel)
791
850
  end
792
851
 
@@ -843,12 +902,14 @@ module Discordrb
843
902
  member = server.member(data['user']['id'].to_i)
844
903
  member.update_roles(data['roles'])
845
904
  member.update_nick(data['nick'])
905
+ member.update_boosting_since(data['premium_since'])
846
906
  end
847
907
 
848
908
  # Internal handler for GUILD_MEMBER_DELETE
849
909
  def delete_guild_member(data)
850
910
  server_id = data['guild_id'].to_i
851
911
  server = self.server(server_id)
912
+ return unless server
852
913
 
853
914
  user_id = data['user']['id'].to_i
854
915
  server.delete_member(user_id)
@@ -951,13 +1012,13 @@ module Discordrb
951
1012
  # Remove the "Bot " prefix if it exists
952
1013
  token = token[4..-1] if token.start_with? 'Bot '
953
1014
 
954
- token = 'Bot ' + token unless type == :user
1015
+ token = "Bot #{token}" unless type == :user
955
1016
  token
956
1017
  end
957
1018
 
958
1019
  def handle_dispatch(type, data)
959
1020
  # Check whether there are still unavailable servers and there have been more than 10 seconds since READY
960
- if @unavailable_servers && @unavailable_servers > 0 && (Time.now - @unavailable_timeout_time) > 10
1021
+ if @unavailable_servers&.positive? && (Time.now - @unavailable_timeout_time) > 10 && !((@intents || 0) & INTENTS[:servers]).zero?
961
1022
  # The server streaming timed out!
962
1023
  LOGGER.debug("Server streaming timed out with #{@unavailable_servers} servers remaining")
963
1024
  LOGGER.debug('Calling ready now because server loading is taking a long time. Servers may be unavailable due to an outage, or your bot is on very large servers.')
@@ -1019,6 +1080,11 @@ module Discordrb
1019
1080
  id = data['guild_id'].to_i
1020
1081
  server = server(id)
1021
1082
  server.process_chunk(data['members'])
1083
+ when :INVITE_CREATE
1084
+ invite = Invite.new(data, self)
1085
+ raise_event(InviteCreateEvent.new(data, invite, self))
1086
+ when :INVITE_DELETE
1087
+ raise_event(InviteDeleteEvent.new(data, self))
1022
1088
  when :MESSAGE_CREATE
1023
1089
  if ignored?(data['author']['id'].to_i)
1024
1090
  debug("Ignored author with ID #{data['author']['id']}")
@@ -1053,6 +1119,10 @@ module Discordrb
1053
1119
  update_message(data)
1054
1120
 
1055
1121
  message = Message.new(data, self)
1122
+
1123
+ event = MessageUpdateEvent.new(message, self)
1124
+ raise_event(event)
1125
+
1056
1126
  return if message.from_bot? && !should_parse_self
1057
1127
 
1058
1128
  unless message.author
@@ -1120,10 +1190,10 @@ module Discordrb
1120
1190
  played_before = presence_user.nil? ? nil : presence_user.game
1121
1191
  update_presence(data)
1122
1192
 
1123
- event = if now_playing != played_before
1124
- PlayingEvent.new(data, self)
1125
- else
1193
+ event = if now_playing == played_before
1126
1194
  PresenceEvent.new(data, self)
1195
+ else
1196
+ PlayingEvent.new(data, self)
1127
1197
  end
1128
1198
 
1129
1199
  raise_event(event)
@@ -1135,7 +1205,8 @@ module Discordrb
1135
1205
  when :VOICE_SERVER_UPDATE
1136
1206
  update_voice_server(data)
1137
1207
 
1138
- # no event as this is irrelevant to users
1208
+ event = VoiceServerUpdateEvent.new(data, self)
1209
+ raise_event(event)
1139
1210
  when :CHANNEL_CREATE
1140
1211
  create_channel(data)
1141
1212
 
@@ -1300,6 +1371,7 @@ module Discordrb
1300
1371
  @event_handlers ||= {}
1301
1372
  handlers = @event_handlers[event.class]
1302
1373
  return unless handlers
1374
+
1303
1375
  handlers.dup.each do |handler|
1304
1376
  call_event(handler, event) if handler.matches?(event)
1305
1377
  end
@@ -1315,7 +1387,7 @@ module Discordrb
1315
1387
  begin
1316
1388
  handler.call(event)
1317
1389
  handler.after_call(event)
1318
- rescue => e
1390
+ rescue StandardError => e
1319
1391
  log_exception(e)
1320
1392
  ensure
1321
1393
  @event_threads.delete(t)
@@ -1328,6 +1400,7 @@ module Discordrb
1328
1400
  @awaits.each do |_, await|
1329
1401
  key, should_delete = await.match(event)
1330
1402
  next unless key
1403
+
1331
1404
  debug("should_delete: #{should_delete}")
1332
1405
  @awaits.delete(await.key) if should_delete
1333
1406
 
@@ -1335,5 +1408,24 @@ module Discordrb
1335
1408
  raise_event(await_event)
1336
1409
  end
1337
1410
  end
1411
+
1412
+ def calculate_intents(intents)
1413
+ intents.reduce(0) do |sum, intent|
1414
+ case intent
1415
+ when Symbol
1416
+ if INTENTS[intent]
1417
+ sum | INTENTS[intent]
1418
+ else
1419
+ LOGGER.warn("Unknown intent: #{intent}")
1420
+ sum
1421
+ end
1422
+ when Integer
1423
+ sum | intent
1424
+ else
1425
+ LOGGER.warn("Invalid intent: #{intent}")
1426
+ sum
1427
+ end
1428
+ end
1429
+ end
1338
1430
  end
1339
1431
  end