discordrb 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +13 -0
  3. data/.devcontainer/devcontainer.json +29 -0
  4. data/.devcontainer/postcreate.sh +4 -0
  5. data/.github/workflows/ci.yml +78 -0
  6. data/.github/workflows/codeql.yml +3 -3
  7. data/.github/workflows/deploy.yml +54 -0
  8. data/.github/workflows/release.yml +45 -0
  9. data/.rubocop.yml +52 -2
  10. data/CHANGELOG.md +95 -0
  11. data/README.md +5 -5
  12. data/discordrb-webhooks.gemspec +1 -1
  13. data/discordrb.gemspec +16 -11
  14. data/lib/discordrb/api/application.rb +84 -8
  15. data/lib/discordrb/api/channel.rb +51 -13
  16. data/lib/discordrb/api/interaction.rb +15 -6
  17. data/lib/discordrb/api/invite.rb +1 -1
  18. data/lib/discordrb/api/server.rb +96 -60
  19. data/lib/discordrb/api/user.rb +12 -2
  20. data/lib/discordrb/api/webhook.rb +20 -5
  21. data/lib/discordrb/api.rb +16 -20
  22. data/lib/discordrb/bot.rb +139 -53
  23. data/lib/discordrb/cache.rb +15 -1
  24. data/lib/discordrb/commands/command_bot.rb +7 -17
  25. data/lib/discordrb/commands/parser.rb +7 -7
  26. data/lib/discordrb/container.rb +46 -0
  27. data/lib/discordrb/data/activity.rb +1 -1
  28. data/lib/discordrb/data/application.rb +1 -0
  29. data/lib/discordrb/data/attachment.rb +23 -3
  30. data/lib/discordrb/data/avatar_decoration.rb +26 -0
  31. data/lib/discordrb/data/call.rb +22 -0
  32. data/lib/discordrb/data/channel.rb +140 -15
  33. data/lib/discordrb/data/collectibles.rb +45 -0
  34. data/lib/discordrb/data/embed.rb +10 -3
  35. data/lib/discordrb/data/emoji.rb +20 -1
  36. data/lib/discordrb/data/integration.rb +3 -0
  37. data/lib/discordrb/data/interaction.rb +164 -27
  38. data/lib/discordrb/data/member.rb +145 -28
  39. data/lib/discordrb/data/message.rb +198 -51
  40. data/lib/discordrb/data/overwrite.rb +2 -0
  41. data/lib/discordrb/data/primary_server.rb +60 -0
  42. data/lib/discordrb/data/profile.rb +2 -7
  43. data/lib/discordrb/data/reaction.rb +2 -1
  44. data/lib/discordrb/data/recipient.rb +1 -1
  45. data/lib/discordrb/data/role.rb +151 -22
  46. data/lib/discordrb/data/server.rb +115 -41
  47. data/lib/discordrb/data/server_preview.rb +68 -0
  48. data/lib/discordrb/data/snapshot.rb +110 -0
  49. data/lib/discordrb/data/user.rb +68 -8
  50. data/lib/discordrb/data/voice_region.rb +1 -0
  51. data/lib/discordrb/data/webhook.rb +2 -5
  52. data/lib/discordrb/data.rb +6 -0
  53. data/lib/discordrb/errors.rb +5 -2
  54. data/lib/discordrb/events/await.rb +1 -1
  55. data/lib/discordrb/events/channels.rb +37 -0
  56. data/lib/discordrb/events/generic.rb +2 -0
  57. data/lib/discordrb/events/guilds.rb +6 -1
  58. data/lib/discordrb/events/interactions.rb +135 -42
  59. data/lib/discordrb/events/invites.rb +2 -0
  60. data/lib/discordrb/events/members.rb +19 -2
  61. data/lib/discordrb/events/message.rb +39 -8
  62. data/lib/discordrb/events/presence.rb +2 -0
  63. data/lib/discordrb/events/raw.rb +1 -0
  64. data/lib/discordrb/events/reactions.rb +2 -0
  65. data/lib/discordrb/events/roles.rb +2 -0
  66. data/lib/discordrb/events/threads.rb +10 -6
  67. data/lib/discordrb/events/typing.rb +1 -0
  68. data/lib/discordrb/events/voice_server_update.rb +1 -0
  69. data/lib/discordrb/events/voice_state_update.rb +1 -0
  70. data/lib/discordrb/events/webhooks.rb +1 -0
  71. data/lib/discordrb/gateway.rb +29 -13
  72. data/lib/discordrb/paginator.rb +3 -3
  73. data/lib/discordrb/permissions.rb +54 -43
  74. data/lib/discordrb/version.rb +1 -1
  75. data/lib/discordrb/websocket.rb +0 -10
  76. data/lib/discordrb.rb +17 -1
  77. metadata +53 -25
  78. data/.circleci/config.yml +0 -152
@@ -11,6 +11,7 @@ module Discordrb
11
11
  ping: 1,
12
12
  command: 2,
13
13
  component: 3,
14
+ autocomplete: 4,
14
15
  modal_submit: 5
15
16
  }.freeze
16
17
 
@@ -22,6 +23,7 @@ module Discordrb
22
23
  deferred_message: 5,
23
24
  deferred_update: 6,
24
25
  update_message: 7,
26
+ autocomplete: 8,
25
27
  modal: 9
26
28
  }.freeze
27
29
 
@@ -89,10 +91,11 @@ module Discordrb
89
91
  # @param flags [Integer] Message flags.
90
92
  # @param ephemeral [true, false] Whether this message should only be visible to the interaction initiator.
91
93
  # @param wait [true, false] Whether this method should return a Message object of the interaction response.
92
- # @param components [Array<#to_h>] An array of components
94
+ # @param components [Array<#to_h>] An array of components.
95
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`.
93
96
  # @yieldparam builder [Webhooks::Builder] An optional message builder. Arguments passed to the method overwrite builder data.
94
97
  # @yieldparam view [Webhooks::View] A builder for creating interaction components.
95
- def respond(content: nil, tts: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: nil, wait: false, components: nil)
98
+ def respond(content: nil, tts: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: nil, wait: false, components: nil, attachments: nil)
96
99
  flags |= 1 << 6 if ephemeral
97
100
 
98
101
  builder = Discordrb::Webhooks::Builder.new
@@ -105,7 +108,7 @@ module Discordrb
105
108
  components ||= view
106
109
  data = builder.to_json_hash
107
110
 
108
- Discordrb::API::Interaction.create_interaction_response(@token, @id, CALLBACK_TYPES[:channel_message], data[:content], tts, data[:embeds], data[:allowed_mentions], flags, components.to_a)
111
+ Discordrb::API::Interaction.create_interaction_response(@token, @id, CALLBACK_TYPES[:channel_message], data[:content], tts, data[:embeds], data[:allowed_mentions], flags, components.to_a, attachments)
109
112
 
110
113
  return unless wait
111
114
 
@@ -157,10 +160,11 @@ module Discordrb
157
160
  # @param flags [Integer] Message flags.
158
161
  # @param ephemeral [true, false] Whether this message should only be visible to the interaction initiator.
159
162
  # @param wait [true, false] Whether this method should return a Message object of the interaction response.
160
- # @param components [Array<#to_h>] An array of components
163
+ # @param components [Array<#to_h>] An array of components.
164
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`.
161
165
  # @yieldparam builder [Webhooks::Builder] An optional message builder. Arguments passed to the method overwrite builder data.
162
166
  # @yieldparam view [Webhooks::View] A builder for creating interaction components.
163
- def update_message(content: nil, tts: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: nil, wait: false, components: nil)
167
+ def update_message(content: nil, tts: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: nil, wait: false, components: nil, attachments: nil)
164
168
  flags |= 1 << 6 if ephemeral
165
169
 
166
170
  builder = Discordrb::Webhooks::Builder.new
@@ -172,7 +176,7 @@ module Discordrb
172
176
  components ||= view
173
177
  data = builder.to_json_hash
174
178
 
175
- Discordrb::API::Interaction.create_interaction_response(@token, @id, CALLBACK_TYPES[:update_message], data[:content], tts, data[:embeds], data[:allowed_mentions], flags, components.to_a)
179
+ Discordrb::API::Interaction.create_interaction_response(@token, @id, CALLBACK_TYPES[:update_message], data[:content], tts, data[:embeds], data[:allowed_mentions], flags, components.to_a, attachments)
176
180
 
177
181
  return unless wait
178
182
 
@@ -184,10 +188,11 @@ module Discordrb
184
188
  # @param content [String] The content of the message.
185
189
  # @param embeds [Array<Hash, Webhooks::Embed>] The embeds for the message.
186
190
  # @param allowed_mentions [Hash, AllowedMentions] Mentions that can ping on this message.
187
- # @param components [Array<#to_h>] An array of components
191
+ # @param components [Array<#to_h>] An array of components.
192
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`.
188
193
  # @return [InteractionMessage] The updated response message.
189
194
  # @yieldparam builder [Webhooks::Builder] An optional message builder. Arguments passed to the method overwrite builder data.
190
- def edit_response(content: nil, embeds: nil, allowed_mentions: nil, components: nil)
195
+ def edit_response(content: nil, embeds: nil, allowed_mentions: nil, components: nil, attachments: nil)
191
196
  builder = Discordrb::Webhooks::Builder.new
192
197
  view = Discordrb::Webhooks::View.new
193
198
 
@@ -196,7 +201,7 @@ module Discordrb
196
201
 
197
202
  components ||= view
198
203
  data = builder.to_json_hash
199
- resp = Discordrb::API::Interaction.edit_original_interaction_response(@token, @application_id, data[:content], data[:embeds], data[:allowed_mentions], components.to_a)
204
+ resp = Discordrb::API::Interaction.edit_original_interaction_response(@token, @application_id, data[:content], data[:embeds], data[:allowed_mentions], components.to_a, attachments)
200
205
 
201
206
  Interactions::Message.new(JSON.parse(resp), @bot, @interaction)
202
207
  end
@@ -212,8 +217,9 @@ module Discordrb
212
217
  # @param allowed_mentions [Hash, AllowedMentions] Mentions that can ping on this message.
213
218
  # @param flags [Integer] Message flags.
214
219
  # @param ephemeral [true, false] Whether this message should only be visible to the interaction initiator.
220
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`.
215
221
  # @yieldparam builder [Webhooks::Builder] An optional message builder. Arguments passed to the method overwrite builder data.
216
- def send_message(content: nil, embeds: nil, tts: false, allowed_mentions: nil, flags: 0, ephemeral: false, components: nil)
222
+ def send_message(content: nil, embeds: nil, tts: false, allowed_mentions: nil, flags: 0, ephemeral: false, components: nil, attachments: nil)
217
223
  flags |= 64 if ephemeral
218
224
 
219
225
  builder = Discordrb::Webhooks::Builder.new
@@ -226,7 +232,7 @@ module Discordrb
226
232
  data = builder.to_json_hash
227
233
 
228
234
  resp = Discordrb::API::Webhook.token_execute_webhook(
229
- @token, @application_id, true, data[:content], nil, nil, tts, nil, data[:embeds], data[:allowed_mentions], flags, components.to_a
235
+ @token, @application_id, true, data[:content], nil, nil, tts, nil, data[:embeds], data[:allowed_mentions], flags, components.to_a, attachments
230
236
  )
231
237
  Interactions::Message.new(JSON.parse(resp), @bot, @interaction)
232
238
  end
@@ -235,8 +241,9 @@ module Discordrb
235
241
  # @param content [String] The message content.
236
242
  # @param embeds [Array<Hash, Webhooks::Embed>] The embeds for the message.
237
243
  # @param allowed_mentions [Hash, AllowedMentions] Mentions that can ping on this message.
244
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`.
238
245
  # @yieldparam builder [Webhooks::Builder] An optional message builder. Arguments passed to the method overwrite builder data.
239
- def edit_message(message, content: nil, embeds: nil, allowed_mentions: nil, components: nil)
246
+ def edit_message(message, content: nil, embeds: nil, allowed_mentions: nil, components: nil, attachments: nil)
240
247
  builder = Discordrb::Webhooks::Builder.new
241
248
  view = Discordrb::Webhooks::View.new
242
249
 
@@ -247,7 +254,7 @@ module Discordrb
247
254
  data = builder.to_json_hash
248
255
 
249
256
  resp = Discordrb::API::Webhook.token_edit_message(
250
- @token, @application_id, message.resolve_id, data[:content], data[:embeds], data[:allowed_mentions], components.to_a
257
+ @token, @application_id, message.resolve_id, data[:content], data[:embeds], data[:allowed_mentions], components.to_a, attachments
251
258
  )
252
259
  Interactions::Message.new(JSON.parse(resp), @bot, @interaction)
253
260
  end
@@ -258,6 +265,14 @@ module Discordrb
258
265
  nil
259
266
  end
260
267
 
268
+ # Show autocomplete choices as a response.
269
+ # @param choices [Array<Hash>, Hash] Array of autocomplete choices to show the user.
270
+ def show_autocomplete_choices(choices)
271
+ choices = choices.map { |name, value| { name: name, value: value } } unless choices.is_a?(Array)
272
+ Discordrb::API::Interaction.create_interaction_response(@token, @id, CALLBACK_TYPES[:autocomplete], nil, nil, nil, nil, nil, nil, nil, choices)
273
+ nil
274
+ end
275
+
261
276
  # @return [Server, nil] This will be nil for interactions that occur in DM channels or servers where the bot
262
277
  # does not have the `bot` scope.
263
278
  def server
@@ -339,6 +354,9 @@ module Discordrb
339
354
  # @return [Integer]
340
355
  attr_reader :id
341
356
 
357
+ # @return [true, false]
358
+ attr_reader :nsfw
359
+
342
360
  # @!visibility private
343
361
  def initialize(data, bot, server_id = nil)
344
362
  @bot = bot
@@ -350,6 +368,7 @@ module Discordrb
350
368
  @description = data['description']
351
369
  @default_permission = data['default_permission']
352
370
  @options = data['options']
371
+ @nsfw = data['nsfw'] || false
353
372
  end
354
373
 
355
374
  # @param subcommand [String, nil] The subcommand to mention.
@@ -372,10 +391,11 @@ module Discordrb
372
391
  # @param name [String] The name to use for this command.
373
392
  # @param description [String] The description of this command.
374
393
  # @param default_permission [true, false] Whether this command is available with default permissions.
394
+ # @param nsfw [true, false] Whether this command should be marked as age-restricted.
375
395
  # @yieldparam (see Bot#edit_application_command)
376
396
  # @return (see Bot#edit_application_command)
377
- def edit(name: nil, description: nil, default_permission: nil, &block)
378
- @bot.edit_application_command(@id, server_id: @server_id, name: name, description: description, default_permission: default_permission, &block)
397
+ def edit(name: nil, description: nil, default_permission: nil, nsfw: nil, &block)
398
+ @bot.edit_application_command(@id, server_id: @server_id, name: name, description: description, default_permission: default_permission, nsfw: nsfw, &block)
379
399
  end
380
400
 
381
401
  # Delete this application command.
@@ -383,6 +403,108 @@ module Discordrb
383
403
  def delete
384
404
  @bot.delete_application_command(@id, server_id: @server_id)
385
405
  end
406
+
407
+ # Get the permission configuration for the this application command on a specific server.
408
+ # @param server_id [Integer, String, nil] The ID of the server to fetch command permissions for.
409
+ # @return [Array<Permission>] the permissions for this application command in the given server.
410
+ def permissions(server_id: nil)
411
+ raise ArgumentError, 'A server ID must be provided for global application commands' if @server_id.nil? && server_id.nil?
412
+
413
+ response = JSON.parse(API::Application.get_application_command_permissions(@bot.token, @bot.profile.id, @server_id || server_id&.resolve_id, @id))
414
+ response['permissions'].map { |permission| Permission.new(permission, response, @bot) }
415
+ rescue Discordrb::Errors::UnknownError
416
+ # If there aren't any explicit overwrites configured for the command, the response is a 400.
417
+ []
418
+ end
419
+
420
+ # An application command permission for a channel, member, or a role.
421
+ class Permission
422
+ # Map of permission types.
423
+ TYPES = {
424
+ role: 1,
425
+ member: 2,
426
+ channel: 3
427
+ }.freeze
428
+
429
+ # @return [Integer] the type of this permission.
430
+ # @see TYPES
431
+ attr_reader :type
432
+
433
+ # @return [Integer] the ID of the thing this permission is for.
434
+ attr_reader :target_id
435
+
436
+ # @return [Integer] the ID of the server this permission is for.
437
+ attr_reader :server_id
438
+
439
+ # @!visibility private
440
+ def initialize(data, command, bot)
441
+ @bot = bot
442
+ @type = data['type']
443
+ @target_id = data['id'].to_i
444
+ @overwrite = data['permission']
445
+ @command_id = command['id'].to_i
446
+ @server_id = command['guild_id'].to_i
447
+ @application_id = command['application_id'].to_i
448
+ end
449
+
450
+ # Whether this permission has been allowed, e.g has a green check in the UI.
451
+ # @return [true, false]
452
+ def allowed?
453
+ @overwrite == true
454
+ end
455
+
456
+ # Whether this permission has been denied, e.g has a red check in the UI.
457
+ # @return [true, false]
458
+ def denied?
459
+ @overwrite == false
460
+ end
461
+
462
+ # Whether this permission is applied to the everyone role in the server.
463
+ # @return [true, false]
464
+ def everyone?
465
+ @target_id == @server_id
466
+ end
467
+
468
+ # Whether this permission is the default for all commands that don't
469
+ # contain explicit permission oerwrites.
470
+ # @return [true, false]
471
+ def default?
472
+ @command_id == @application_id
473
+ end
474
+
475
+ # Whether this permission is applied to every channel in the server.
476
+ # @return [true, false]
477
+ def all_channels?
478
+ @target_id == (@server_id - 1)
479
+ end
480
+
481
+ # Get the user, role, or channel(s) that this permission targets.
482
+ # @return [Array<Channel>, Role, Member]
483
+ def target
484
+ case @type
485
+ when TYPES[:role]
486
+ @bot.server(@server_id).role(@target_id)
487
+ when TYPES[:member]
488
+ @bot.server(@server_id).member(@target_id)
489
+ when TYPES[:channel]
490
+ all_channels ? @bot.server(@server_id).channels : [@bot.channel(@target_id)]
491
+ end
492
+ end
493
+
494
+ alias_method :targets, :target
495
+
496
+ # @!method role?
497
+ # @return [true, false] whether this permission is for a role.
498
+ # @!method member?
499
+ # @return [true, false] whether this permission is for a member.
500
+ # @!method channel?
501
+ # @return [true, false] whether this permission is for a channel.
502
+ TYPES.each do |name, value|
503
+ define_method("#{name}?") do
504
+ @type == value
505
+ end
506
+ end
507
+ end
386
508
  end
387
509
 
388
510
  # Objects specific to Interactions.
@@ -466,19 +588,27 @@ module Discordrb
466
588
  # @param name [String, Symbol] The name of the argument.
467
589
  # @param description [String] A description of the argument.
468
590
  # @param required [true, false] Whether this option must be provided.
591
+ # @param min_length [Integer] A minimum length for option value.
592
+ # @param max_length [Integer] A maximum length for option value.
469
593
  # @param choices [Hash, nil] Available choices, mapped as `Name => Value`.
594
+ # @param autocomplete [true, false] Whether this option can dynamically show choices.
470
595
  # @return (see #option)
471
- def string(name, description, required: nil, choices: nil)
472
- option(TYPES[:string], name, description, required: required, choices: choices)
596
+ def string(name, description, required: nil, min_length: nil, max_length: nil, choices: nil, autocomplete: nil)
597
+ option(TYPES[:string], name, description,
598
+ required: required, min_length: min_length, max_length: max_length, choices: choices, autocomplete: autocomplete)
473
599
  end
474
600
 
475
601
  # @param name [String, Symbol] The name of the argument.
476
602
  # @param description [String] A description of the argument.
477
603
  # @param required [true, false] Whether this option must be provided.
604
+ # @param min_value [Integer] A minimum value for option.
605
+ # @param max_value [Integer] A maximum value for option.
478
606
  # @param choices [Hash, nil] Available choices, mapped as `Name => Value`.
607
+ # @param autocomplete [true, false] Whether this option can dynamically show choices.
479
608
  # @return (see #option)
480
- def integer(name, description, required: nil, choices: nil)
481
- option(TYPES[:integer], name, description, required: required, choices: choices)
609
+ def integer(name, description, required: nil, min_value: nil, max_value: nil, choices: nil, autocomplete: nil)
610
+ option(TYPES[:integer], name, description,
611
+ required: required, min_value: min_value, max_value: max_value, choices: choices, autocomplete: autocomplete)
482
612
  end
483
613
 
484
614
  # @param name [String, Symbol] The name of the argument.
@@ -526,10 +656,13 @@ module Discordrb
526
656
  # @param name [String, Symbol] The name of the argument.
527
657
  # @param description [String] A description of the argument.
528
658
  # @param required [true, false] Whether this option must be provided.
659
+ # @param min_value [Float] A minimum value for option.
660
+ # @param max_value [Float] A maximum value for option.
661
+ # @param autocomplete [true, false] Whether this option can dynamically show choices.
529
662
  # @return (see #option)
530
- def number(name, description, required: nil, min_value: nil, max_value: nil, choices: nil)
663
+ def number(name, description, required: nil, min_value: nil, max_value: nil, choices: nil, autocomplete: nil)
531
664
  option(TYPES[:number], name, description,
532
- required: required, min_value: min_value, max_value: max_value, choices: choices)
665
+ required: required, min_value: min_value, max_value: max_value, choices: choices, autocomplete: autocomplete)
533
666
  end
534
667
 
535
668
  # @param name [String, Symbol] The name of the argument.
@@ -547,15 +680,19 @@ module Discordrb
547
680
  # @param required [true, false] Whether this option must be provided.
548
681
  # @param min_value [Integer, Float] A minimum value for integer and number options.
549
682
  # @param max_value [Integer, Float] A maximum value for integer and number options.
683
+ # @param min_length [Integer] A minimum length for string option value.
684
+ # @param max_length [Integer] A maximum length for string option value.
550
685
  # @param channel_types [Array<Integer>] Channel types that can be provides for channel options.
686
+ # @param autocomplete [true, false] Whether this option can dynamically show options.
551
687
  # @return Hash
552
688
  def option(type, name, description, required: nil, choices: nil, options: nil, min_value: nil, max_value: nil,
553
- channel_types: nil)
689
+ min_length: nil, max_length: nil, channel_types: nil, autocomplete: nil)
554
690
  opt = { type: type, name: name, description: description }
555
691
  choices = choices.map { |option_name, value| { name: option_name, value: value } } if choices
556
692
 
557
693
  opt.merge!({ required: required, choices: choices, options: options, min_value: min_value,
558
- max_value: max_value, channel_types: channel_types }.compact)
694
+ max_value: max_value, min_length: min_length, max_length: max_length,
695
+ channel_types: channel_types, autocomplete: autocomplete }.compact)
559
696
 
560
697
  @options << opt
561
698
  opt
@@ -766,8 +903,8 @@ module Discordrb
766
903
  # Respond to this message.
767
904
  # @param (see Interaction#send_message)
768
905
  # @yieldparam (see Interaction#send_message)
769
- def respond(content: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: true, components: nil, &block)
770
- @interaction.send_message(content: content, embeds: embeds, allowed_mentions: allowed_mentions, flags: flags, ephemeral: ephemeral, components: components, &block)
906
+ def respond(content: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: true, components: nil, attachments: nil, &block)
907
+ @interaction.send_message(content: content, embeds: embeds, allowed_mentions: allowed_mentions, flags: flags, ephemeral: ephemeral, components: components, attachments: attachments, &block)
771
908
  end
772
909
 
773
910
  # Delete this message.
@@ -780,8 +917,8 @@ module Discordrb
780
917
  # @param embeds (see Interaction#send_message)
781
918
  # @param allowed_mentions (see Interaction#send_message)
782
919
  # @yieldparam (see Interaction#send_message)
783
- def edit(content: nil, embeds: nil, allowed_mentions: nil, components: nil, &block)
784
- @interaction.edit_message(@id, content: content, embeds: embeds, allowed_mentions: allowed_mentions, components: components, &block)
920
+ def edit(content: nil, embeds: nil, allowed_mentions: nil, components: nil, attachments: nil, &block)
921
+ @interaction.edit_message(@id, content: content, embeds: embeds, allowed_mentions: allowed_mentions, components: components, attachments: attachments, &block)
785
922
  end
786
923
 
787
924
  # @return [Discordrb::Message]
@@ -3,6 +3,19 @@
3
3
  module Discordrb
4
4
  # Mixin for the attributes members and private members should have
5
5
  module MemberAttributes
6
+ # Map of server member flags
7
+ MEMBER_FLAGS = {
8
+ rejoined: 1 << 0,
9
+ completed_onboarding: 1 << 1,
10
+ bypassed_verification: 1 << 2,
11
+ started_onboarding: 1 << 3,
12
+ guest: 1 << 4,
13
+ started_home_actions: 1 << 5,
14
+ completed_home_actions: 1 << 6,
15
+ automod_quarantined_username: 1 << 7,
16
+ dm_settings_upsell_acknowledged: 1 << 9
17
+ }.freeze
18
+
6
19
  # @return [Time] when this member joined the server.
7
20
  attr_reader :joined_at
8
21
 
@@ -22,6 +35,44 @@ module Discordrb
22
35
  # @return [Time] When the user's timeout will expire.
23
36
  attr_reader :communication_disabled_until
24
37
  alias_method :timeout, :communication_disabled_until
38
+
39
+ # @return [Integer] the flags set on this member.
40
+ attr_reader :flags
41
+
42
+ # @return [true, false] whether the member has not yet passed the server's membership screening requirements.
43
+ attr_reader :pending
44
+ alias_method :pending?, :pending
45
+
46
+ # @return [String, nil] the ID of this user's current avatar, can be used to generate a server avatar URL.
47
+ # @see #server_avatar_url
48
+ attr_reader :server_avatar_id
49
+
50
+ # @return [String, nil] the ID of this user's current server banner, can be used to generate a banner URL.
51
+ # @see #server_banner_url
52
+ attr_reader :server_banner_id
53
+
54
+ # @return [AvatarDecoration, nil] the user's current server avatar decoration, or nil for no server avatar decoration.
55
+ attr_reader :server_avatar_decoration
56
+
57
+ # Utility method to get a member's server avatar URL.
58
+ # @param format [String, nil] If `nil`, the URL will default to `webp` for static avatars, and will detect if the member has a `gif` avatar. You can otherwise specify one of `webp`, `jpg`, `png`, or `gif` to override this.
59
+ # @return [String, nil] the URL to the avatar image, or nil if the member doesn't have one.
60
+ def server_avatar_url(format = nil)
61
+ API::Server.avatar_url(@server_id, @user.id, @server_avatar_id, format) if @server_avatar_id
62
+ end
63
+
64
+ # Utility method to get a member's server banner URL.
65
+ # @param format [String, nil] If `nil`, the URL will default to `webp` for static banners, and will detect if the member has a `gif` banner. You can otherwise specify one of `webp`, `jpg`, `png`, or `gif` to override this.
66
+ # @return [String, nil] the URL to the banner image, or nil if the member doesn't have one.
67
+ def server_banner_url(format = nil)
68
+ API::Server.banner_url(@server_id, @user.id, @server_banner_id, format) if @server_banner_id
69
+ end
70
+
71
+ MEMBER_FLAGS.each do |name, value|
72
+ define_method("#{name}?") do
73
+ @flags.anybits?(value)
74
+ end
75
+ end
25
76
  end
26
77
 
27
78
  # A member is a user on a server. It differs from regular users in that it has roles, voice statuses and things like
@@ -64,7 +115,7 @@ module Discordrb
64
115
  @bot = bot
65
116
 
66
117
  @user = bot.ensure_user(data['user'])
67
- super @user # Initialize the delegate class
118
+ super(@user) # Initialize the delegate class
68
119
 
69
120
  @server = server
70
121
  @server_id = server&.id || data['guild_id'].to_i
@@ -77,6 +128,11 @@ module Discordrb
77
128
  timeout_until = data['communication_disabled_until']
78
129
  @communication_disabled_until = timeout_until ? Time.parse(timeout_until) : nil
79
130
  @permissions = Permissions.new(data['permissions']) if data['permissions']
131
+ @server_avatar_id = data['avatar']
132
+ @server_banner_id = data['banner']
133
+ @flags = data['flags'] || 0
134
+ @pending = data.key?('pending') ? data['pending'] : false
135
+ @server_avatar_decoration = process_avatar_decoration(data['avatar_decoration_data'])
80
136
  end
81
137
 
82
138
  # @return [Server] the server this member is on.
@@ -136,7 +192,7 @@ module Discordrb
136
192
  def communication_disabled_until=(timeout_until)
137
193
  raise ArgumentError, 'A time out cannot exceed 28 days' if timeout_until && timeout_until > (Time.now + 2_419_200)
138
194
 
139
- API::Server.update_member(@bot.token, @server_id, @user.id, communication_disabled_until: timeout_until.iso8601)
195
+ update_member_data(communication_disabled_until: timeout_until&.iso8601)
140
196
  end
141
197
 
142
198
  alias_method :timeout=, :communication_disabled_until=
@@ -146,7 +202,7 @@ module Discordrb
146
202
  # @param reason [String] The reason the user's roles are being changed.
147
203
  def set_roles(role, reason = nil)
148
204
  role_ids = role_id_array(role)
149
- API::Server.update_member(@bot.token, @server_id, @user.id, roles: role_ids, reason: reason)
205
+ update_member_data(roles: role_ids, reason: reason)
150
206
  end
151
207
 
152
208
  # Adds and removes roles from a member.
@@ -163,7 +219,7 @@ module Discordrb
163
219
  old_role_ids = resolve_role_ids
164
220
  new_role_ids = (old_role_ids - remove_role_ids + add_role_ids).uniq
165
221
 
166
- API::Server.update_member(@bot.token, @server_id, @user.id, roles: new_role_ids, reason: reason)
222
+ update_member_data(roles: new_role_ids, reason: reason)
167
223
  end
168
224
 
169
225
  # Adds one or more roles to this member.
@@ -172,12 +228,12 @@ module Discordrb
172
228
  def add_role(role, reason = nil)
173
229
  role_ids = role_id_array(role)
174
230
 
175
- if role_ids.count == 1
231
+ if role_ids.count.one?
176
232
  API::Server.add_member_role(@bot.token, @server_id, @user.id, role_ids[0], reason)
177
233
  else
178
234
  old_role_ids = resolve_role_ids
179
235
  new_role_ids = (old_role_ids + role_ids).uniq
180
- API::Server.update_member(@bot.token, @server_id, @user.id, roles: new_role_ids, reason: reason)
236
+ update_member_data(roles: new_role_ids, reason: reason)
181
237
  end
182
238
  end
183
239
 
@@ -187,12 +243,12 @@ module Discordrb
187
243
  def remove_role(role, reason = nil)
188
244
  role_ids = role_id_array(role)
189
245
 
190
- if role_ids.count == 1
246
+ if role_ids.count.one?
191
247
  API::Server.remove_member_role(@bot.token, @server_id, @user.id, role_ids[0], reason)
192
248
  else
193
249
  old_role_ids = resolve_role_ids
194
250
  new_role_ids = old_role_ids.reject { |i| role_ids.include?(i) }
195
- API::Server.update_member(@bot.token, @server_id, @user.id, roles: new_role_ids, reason: reason)
251
+ update_member_data(roles: new_role_ids, reason: reason)
196
252
  end
197
253
  end
198
254
 
@@ -216,6 +272,7 @@ module Discordrb
216
272
 
217
273
  coloured_roles.max_by(&:position)
218
274
  end
275
+
219
276
  alias_method :color_role, :colour_role
220
277
 
221
278
  # @return [ColourRGB, nil] the colour this member has.
@@ -224,33 +281,39 @@ module Discordrb
224
281
 
225
282
  colour_role.color
226
283
  end
284
+
227
285
  alias_method :color, :colour
228
286
 
229
287
  # Server deafens this member.
230
- def server_deafen
231
- API::Server.update_member(@bot.token, @server_id, @user.id, deaf: true)
288
+ # @param reason [String, nil] The reason for defeaning this member.
289
+ def server_deafen(reason: nil)
290
+ update_member_data(deaf: true, reason: reason)
232
291
  end
233
292
 
234
293
  # Server undeafens this member.
235
- def server_undeafen
236
- API::Server.update_member(@bot.token, @server_id, @user.id, deaf: false)
294
+ # @param reason [String, nil] The reason for un-defeaning this member.
295
+ def server_undeafen(reason: nil)
296
+ update_member_data(deaf: false, reason: reason)
237
297
  end
238
298
 
239
299
  # Server mutes this member.
240
- def server_mute
241
- API::Server.update_member(@bot.token, @server_id, @user.id, mute: true)
300
+ # @param reason [String, nil] The reason for muting this member.
301
+ def server_mute(reason: nil)
302
+ update_member_data(mute: true, reason: reason)
242
303
  end
243
304
 
244
305
  # Server unmutes this member.
245
- def server_unmute
246
- API::Server.update_member(@bot.token, @server_id, @user.id, mute: false)
306
+ # @param reason [String, nil] The reason for un-muting this member.
307
+ def server_unmute(reason: nil)
308
+ update_member_data(mute: false, reason: reason)
247
309
  end
248
310
 
249
311
  # Bans this member from the server.
250
- # @param message_days [Integer] How many days worth of messages sent by the member should be deleted.
312
+ # @param message_days [Integer] How many days worth of messages sent by the member should be deleted. This parameter is deprecated and will be removed in 4.0.
313
+ # @param message_seconds [Integer] How many seconds worth of messages sent by the member should be deleted.
251
314
  # @param reason [String] The reason this member is being banned.
252
- def ban(message_days = 0, reason: nil)
253
- server.ban(@user, message_days, reason: reason)
315
+ def ban(message_days = 0, message_seconds: nil, reason: nil)
316
+ server.ban(@user, message_days, message_seconds: message_seconds, reason: reason)
254
317
  end
255
318
 
256
319
  # Unbans this member from the server.
@@ -277,13 +340,10 @@ module Discordrb
277
340
  # @param nick [String, nil] The string to set the nickname to, or nil if it should be reset.
278
341
  # @param reason [String] The reason the user's nickname is being changed.
279
342
  def set_nick(nick, reason = nil)
280
- # Discord uses the empty string to signify 'no nickname' so we convert nil into that
281
- nick ||= ''
282
-
283
343
  if @user.current_bot?
284
- API::User.change_own_nickname(@bot.token, @server_id, nick, reason)
344
+ update_current_member_data(nick: nick, reason: reason)
285
345
  else
286
- API::Server.update_member(@bot.token, @server_id, @user.id, nick: nick, reason: nil)
346
+ update_member_data(nick: nick, reason: reason)
287
347
  end
288
348
  end
289
349
 
@@ -294,6 +354,29 @@ module Discordrb
294
354
  nickname || global_name || username
295
355
  end
296
356
 
357
+ # @param format [String, nil] If `nil`, the URL will default to `webp` for static avatars, and will detect if the member has a `gif` avatar. You can otherwise specify one of `webp`, `jpg`, `png`, or `gif` to override this.
358
+ # @return [String, nil] the avatar that the user has displayed (server avatar if they have one, user avatar if they have one, nil otherwise)
359
+ def display_avatar_url(format = nil)
360
+ server_avatar_url(format) || avatar_url(format)
361
+ end
362
+
363
+ # @param format [String, nil] If `nil`, the URL will default to `webp` for static banners, and will detect if the member has a `gif` banner. You can otherwise specify one of `webp`, `jpg`, `png`, or `gif` to override this.
364
+ # @return [String, nil] the banner that the user has displayed (server banner if they have one, user banner if they have one, nil otherwise)
365
+ def display_banner_url(format = nil)
366
+ server_banner_url(format) || banner_url(format)
367
+ end
368
+
369
+ # @return [AvatarDecoration, nil] the avatar decoration that the user displays (server avatar decoration if they have one, user avatar decoration if they have one, nil otherwise)
370
+ def display_avatar_decoration
371
+ server_avatar_decoration || avatar_decoration
372
+ end
373
+
374
+ # Set the flags for this member.
375
+ # @param flags [Integer, nil] The new bitwise value of flags for this member, or nil.
376
+ def flags=(flags)
377
+ update_member_data(flags: flags)
378
+ end
379
+
297
380
  # Update this member's roles
298
381
  # @note For internal use only.
299
382
  # @!visibility private
@@ -332,20 +415,41 @@ module Discordrb
332
415
  # @!visibility private
333
416
  def update_data(data)
334
417
  update_roles(data['roles']) if data['roles']
335
- update_nick(data['nick']) if data.key?('nick')
418
+ @nick = data['nick'] if data.key?('nick')
336
419
  @mute = data['mute'] if data.key?('mute')
337
420
  @deaf = data['deaf'] if data.key?('deaf')
421
+ @server_avatar_id = data['avatar'] if data.key?('avatar')
422
+ @server_banner_id = data['banner'] if data.key?('banner')
423
+ @flags = data['flags'] if data.key?('flags')
424
+ @pending = data['pending'] if data.key?('pending')
338
425
 
339
426
  @joined_at = Time.parse(data['joined_at']) if data['joined_at']
340
- timeout_until = data['communication_disabled_until']
341
- @communication_disabled_until = timeout_until ? Time.parse(timeout_until) : nil
427
+
428
+ if data.key?('communication_disabled_until')
429
+ timeout_until = data['communication_disabled_until']
430
+ @communication_disabled_until = timeout_until ? Time.parse(timeout_until) : nil
431
+ end
432
+
433
+ if data.key('premium_since')
434
+ @boosting_since = data['premium_since'] ? Time.parse(data['premium_since']) : nil
435
+ end
436
+
437
+ if (user = data['user'])
438
+ @user.update_global_name(user['global_name']) if user['global_name']
439
+ @user.avatar_id = user['avatar'] if user.key('avatar')
440
+ @user.update_avatar_decoration(user['avatar_decoration_data']) if user.key?('avatar_decoration_data')
441
+ @user.update_collectibles(user['collectibles']) if user.key?('collectibles')
442
+ @user.update_primary_server(user['primary_guild']) if user.key?('primary_guild')
443
+ end
444
+
445
+ @server_avatar_decoration = process_avatar_decoration(data['avatar_decoration_data']) if data.key?('avatar_decoration_data')
342
446
  end
343
447
 
344
448
  include PermissionCalculator
345
449
 
346
450
  # Overwriting inspect for debug purposes
347
451
  def inspect
348
- "<Member user=#{@user.inspect} server=#{@server&.inspect || @server_id} joined_at=#{@joined_at} roles=#{@roles&.inspect || @role_ids} voice_channel=#{@voice_channel.inspect} mute=#{@mute} deaf=#{@deaf} self_mute=#{@self_mute} self_deaf=#{@self_deaf}>"
452
+ "<Member user=#{@user.inspect} server=#{@server&.inspect || @server_id} joined_at=#{@joined_at} roles=#{@roles&.inspect || @role_ids} voice_channel=#{voice_channel.inspect} mute=#{mute} deaf=#{deaf} self_mute=#{self_mute} self_deaf=#{self_deaf}>"
349
453
  end
350
454
 
351
455
  private
@@ -365,8 +469,21 @@ module Discordrb
365
469
  voice_state&.send name
366
470
  end
367
471
 
472
+ # @!visibility private
368
473
  def resolve_role_ids
369
474
  @roles ? @roles.collect(&:id) : @role_ids
370
475
  end
476
+
477
+ # @!visibility private
478
+ def update_member_data(new_data)
479
+ update_data(JSON.parse(API::Server.update_member(@bot.token, @server_id, @user.id, **new_data)))
480
+ end
481
+
482
+ # @!visibility private
483
+ def update_current_member_data(new_data)
484
+ update_data(JSON.parse(API::Server.update_current_member(@bot.token, @server_id,
485
+ new_data.key?(:nick) ? new_data[:nick] : :undef,
486
+ new_data[:reason])))
487
+ end
371
488
  end
372
489
  end