discordrb 3.2.1 → 3.3.0

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

Potentially problematic release.


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

Files changed (47) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +4 -0
  3. data/.rubocop.yml +3 -3
  4. data/.travis.yml +28 -3
  5. data/.yardopts +1 -1
  6. data/CHANGELOG.md +555 -144
  7. data/CONTRIBUTING.md +1 -1
  8. data/Gemfile +0 -4
  9. data/README.md +86 -15
  10. data/Rakefile +2 -2
  11. data/bin/travis_build_docs.sh +17 -0
  12. data/discordrb-webhooks.gemspec +2 -1
  13. data/discordrb.gemspec +12 -5
  14. data/lib/discordrb.rb +2 -2
  15. data/lib/discordrb/api.rb +94 -25
  16. data/lib/discordrb/api/channel.rb +53 -17
  17. data/lib/discordrb/api/invite.rb +7 -4
  18. data/lib/discordrb/api/server.rb +173 -36
  19. data/lib/discordrb/api/user.rb +18 -4
  20. data/lib/discordrb/api/webhook.rb +83 -0
  21. data/lib/discordrb/await.rb +1 -1
  22. data/lib/discordrb/bot.rb +191 -102
  23. data/lib/discordrb/cache.rb +39 -9
  24. data/lib/discordrb/commands/command_bot.rb +79 -24
  25. data/lib/discordrb/commands/container.rb +16 -2
  26. data/lib/discordrb/commands/parser.rb +46 -7
  27. data/lib/discordrb/commands/rate_limiter.rb +8 -6
  28. data/lib/discordrb/container.rb +51 -7
  29. data/lib/discordrb/data.rb +1729 -286
  30. data/lib/discordrb/errors.rb +34 -1
  31. data/lib/discordrb/events/generic.rb +1 -1
  32. data/lib/discordrb/events/guilds.rb +1 -0
  33. data/lib/discordrb/events/message.rb +18 -12
  34. data/lib/discordrb/events/presence.rb +7 -2
  35. data/lib/discordrb/events/reactions.rb +13 -4
  36. data/lib/discordrb/events/roles.rb +7 -6
  37. data/lib/discordrb/events/typing.rb +1 -1
  38. data/lib/discordrb/events/webhooks.rb +61 -0
  39. data/lib/discordrb/gateway.rb +85 -32
  40. data/lib/discordrb/light.rb +1 -1
  41. data/lib/discordrb/logger.rb +8 -7
  42. data/lib/discordrb/permissions.rb +41 -4
  43. data/lib/discordrb/version.rb +1 -1
  44. data/lib/discordrb/voice/encoder.rb +10 -8
  45. data/lib/discordrb/voice/voice_bot.rb +4 -4
  46. data/lib/discordrb/websocket.rb +2 -2
  47. metadata +59 -14
@@ -37,7 +37,7 @@ module Discordrb
37
37
  register_event(MessageEvent, attributes, block)
38
38
  end
39
39
 
40
- # This **event** is raised when the READY packet is received, i. e. servers and channels have finished
40
+ # This **event** is raised when the READY packet is received, i.e. servers and channels have finished
41
41
  # initialization. It's the recommended way to do things when the bot has finished starting up.
42
42
  # @param attributes [Hash] Event attributes, none in this particular case
43
43
  # @yield The block is executed when the event is raised.
@@ -237,7 +237,8 @@ module Discordrb
237
237
  register_event(ChannelRecipientRemoveEvent, attributes, block)
238
238
  end
239
239
 
240
- # This **event** is raised when a user's voice state changes.
240
+ # This **event** is raised when a user's voice state changes. This includes when a user joins, leaves, or
241
+ # moves between voice channels, as well as their mute and deaf status for themselves and on the server.
241
242
  # @param attributes [Hash] The event's attributes.
242
243
  # @option attributes [String, Integer, User] :from Matches the user that sent the message.
243
244
  # @option attributes [String, Integer, Channel] :channel Matches the voice channel the user has joined.
@@ -263,7 +264,8 @@ module Discordrb
263
264
  register_event(ServerMemberAddEvent, attributes, block)
264
265
  end
265
266
 
266
- # This **event** is raised when a member update happens.
267
+ # This **event** is raised when a member update happens. This includes when a members nickname
268
+ # or roles are updated.
267
269
  # @param attributes [Hash] The event's attributes.
268
270
  # @option attributes [String] :username Matches the username of the updated user.
269
271
  # @yield The block is executed when the event is raised.
@@ -305,9 +307,8 @@ module Discordrb
305
307
  register_event(UserUnbanEvent, attributes, block)
306
308
  end
307
309
 
308
- # This **event** is raised when a server is created respective to the bot, i. e. the bot joins a server or creates
309
- # a new one itself. It should never be necessary to listen to this event as it will only ever be triggered by
310
- # things the bot itself does, but one can never know.
310
+ # This **event** is raised when a server is created respective to the bot, i.e. the bot joins a server or creates
311
+ # a new one itself.
311
312
  # @param attributes [Hash] The event's attributes.
312
313
  # @option attributes [String, Integer, Server] :server Matches the server that was created.
313
314
  # @yield The block is executed when the event is raised.
@@ -385,6 +386,48 @@ module Discordrb
385
386
  register_event(ServerEmojiUpdateEvent, attributes, block)
386
387
  end
387
388
 
389
+ # This **event** is raised when a role is created.
390
+ # @param attributes [Hash] The event's attributes.
391
+ # @option attributes [String] :name Matches the role name.
392
+ # @yield The block is executed when the event is raised.
393
+ # @yieldparam event [ServerRoleCreateEvent] The event that was raised.
394
+ # @return [ServerRoleCreateEventHandler] the event handler that was registered.
395
+ def server_role_create(attributes = {}, &block)
396
+ register_event(ServerRoleCreateEvent, attributes, block)
397
+ end
398
+
399
+ # This **event** is raised when a role is deleted.
400
+ # @param attributes [Hash] The event's attributes.
401
+ # @option attributes [#resolve_id] :id Matches the role id.
402
+ # @yield The block is executed when the event is raised.
403
+ # @yieldparam event [ServerRoleDeleteEvent] The event that was raised.
404
+ # @return [ServerRoleDeleteEventHandler] the event handler that was registered.
405
+ def server_role_delete(attributes = {}, &block)
406
+ register_event(ServerRoleDeleteEvent, attributes, block)
407
+ end
408
+
409
+ # This **event** is raised when a role is updated.
410
+ # @param attributes [Hash] The event's attributes.
411
+ # @option attributes [String] :name Matches the role name.
412
+ # @yield The block is executed when the event is raised.
413
+ # @yieldparam event [ServerRoleUpdateEvent] The event that was raised.
414
+ # @return [ServerRoleUpdateEventHandler] the event handler that was registered.
415
+ def server_role_update(attributes = {}, &block)
416
+ register_event(ServerRoleUpdateEvent, attributes, block)
417
+ end
418
+
419
+ # This **event** is raised when a webhook is updated.
420
+ # @param attributes [Hash] The event's attributes.
421
+ # @option attributes [String, Integer, Server] :server Matches the server by name, id or instance.
422
+ # @option attributes [String, Integer, Channel] :channel Matches the channel by name, id or instance.
423
+ # @option attribute [String, Integer, Webhook] :webhook Matches the webhook by name, id or instance.
424
+ # @yield The block is executed when the event is raised.
425
+ # @yieldparam event [WebhookUpdateEvent] The event that was raised.
426
+ # @return [WebhookUpdateEventHandler] the event handler that was registered.
427
+ def webhook_update(attributes = {}, &block)
428
+ register_event(WebhookUpdateEvent, attributes, block)
429
+ end
430
+
388
431
  # This **event** is raised when an {Await} is triggered. It provides an easy way to execute code
389
432
  # on an await without having to rely on the await's block.
390
433
  # @param attributes [Hash] The event's attributes.
@@ -460,6 +503,7 @@ module Discordrb
460
503
  def add_handler(handler)
461
504
  clazz = EventContainer.event_class(handler.class)
462
505
  @event_handlers ||= {}
506
+ @event_handlers[clazz] ||= []
463
507
  @event_handlers[clazz] << handler
464
508
  end
465
509
 
@@ -487,7 +531,7 @@ module Discordrb
487
531
  # Returns the event class for a handler class type
488
532
  # @see #handler_class
489
533
  # @param handler_class [Class] The handler type
490
- # @return [Class, nil] the event type, or nil if the handler_class isn't a handler class (i. e. ends with Handler)
534
+ # @return [Class, nil] the event type, or nil if the handler_class isn't a handler class (i.e. ends with Handler)
491
535
  def self.event_class(handler_class)
492
536
  class_name = handler_class.to_s
493
537
  return nil unless class_name.end_with? 'Handler'
@@ -2,7 +2,6 @@
2
2
 
3
3
  # These classes hold relevant Discord data, such as messages or channels.
4
4
 
5
- require 'ostruct'
6
5
  require 'discordrb/permissions'
7
6
  require 'discordrb/errors'
8
7
  require 'discordrb/api'
@@ -10,6 +9,7 @@ require 'discordrb/api/channel'
10
9
  require 'discordrb/api/server'
11
10
  require 'discordrb/api/invite'
12
11
  require 'discordrb/api/user'
12
+ require 'discordrb/api/webhook'
13
13
  require 'discordrb/webhooks/embeds'
14
14
  require 'time'
15
15
  require 'base64'
@@ -71,12 +71,15 @@ module Discordrb
71
71
  # @return [Integer] the ID which uniquely identifies this object across Discord.
72
72
  attr_reader :id
73
73
  alias_method :resolve_id, :id
74
+ alias_method :hash, :id
74
75
 
75
76
  # ID based comparison
76
77
  def ==(other)
77
78
  Discordrb.id_compare(@id, other)
78
79
  end
79
80
 
81
+ alias_method :eql?, :==
82
+
80
83
  # Estimates the time this object was generated on based on the beginning of the ID. This is fairly accurate but
81
84
  # shouldn't be relied on as Discord might change its algorithm at any time
82
85
  # @return [Time] when this object was created at
@@ -125,16 +128,18 @@ module Discordrb
125
128
  "<@#{@id}>"
126
129
  end
127
130
 
128
- # Utility function to get Discord's distinct representation of a user, i. e. username + discriminator
131
+ # Utility function to get Discord's distinct representation of a user, i.e. username + discriminator
129
132
  # @return [String] distinct representation of user
130
133
  def distinct
131
134
  "#{@username}##{@discriminator}"
132
135
  end
133
136
 
134
137
  # Utility function to get a user's avatar URL.
138
+ # @param format [String, nil] If `nil`, the URL will default to `webp` for static avatars, and will detect if the user has a `gif` avatar. You can otherwise specify one of `webp`, `jpg`, `png`, or `gif` to override this. Will always be PNG for default avatars.
135
139
  # @return [String] the URL to the avatar image.
136
- def avatar_url
137
- API::User.avatar_url(@id, @avatar_id)
140
+ def avatar_url(format = nil)
141
+ return API::User.default_avatar(@discriminator) unless @avatar_id
142
+ API::User.avatar_url(@id, @avatar_id, format)
138
143
  end
139
144
  end
140
145
 
@@ -196,6 +201,8 @@ module Discordrb
196
201
  # @param file [File] The file to send to the user
197
202
  # @param caption [String] The caption of the file being sent
198
203
  # @return [Message] the message sent to this user.
204
+ # @example Send a file from disk
205
+ # user.send_file(File.open('rubytaco.png', 'r'))
199
206
  def send_file(file, caption = nil)
200
207
  pm.send_file(file, caption: caption)
201
208
  end
@@ -231,6 +238,13 @@ module Discordrb
231
238
  @bot.add_await(key, Discordrb::Events::MessageEvent, { from: @id }.merge(attributes), &block)
232
239
  end
233
240
 
241
+ # Add a blocking await for a message from this user. Specifically, this adds a global await for a MessageEvent with this
242
+ # user's ID as a :from attribute.
243
+ # @see Bot#add_await!
244
+ def await!(attributes = {})
245
+ @bot.add_await!(Discordrb::Events::MessageEvent, { from: @id }.merge(attributes))
246
+ end
247
+
234
248
  # Gets the member this user is on a server
235
249
  # @param server [Server] The server to get the member for
236
250
  # @return [Member] this user as a member on a particular server
@@ -250,7 +264,7 @@ module Discordrb
250
264
  @discriminator == Message::ZERO_DISCRIM
251
265
  end
252
266
 
253
- [:offline, :idle, :online].each do |e|
267
+ %i[offline idle online].each do |e|
254
268
  define_method(e.to_s + '?') do
255
269
  @status.to_sym == e
256
270
  end
@@ -272,14 +286,14 @@ module Discordrb
272
286
  # @return [String] the application description
273
287
  attr_reader :description
274
288
 
275
- # @return [Array<String>] the applications origins permitted to use RPC
289
+ # @return [Array<String>] the application's origins permitted to use RPC
276
290
  attr_reader :rpc_origins
277
291
 
278
292
  # @return [Integer]
279
293
  attr_reader :flags
280
294
 
281
295
  # Gets the user object of the owner. May be limited to username, discriminator,
282
- # ID and avatar if the bot cannot reach the owner.
296
+ # ID, and avatar if the bot cannot reach the owner.
283
297
  # @return [User] the user object of the owner
284
298
  attr_reader :owner
285
299
 
@@ -296,7 +310,7 @@ module Discordrb
296
310
  end
297
311
 
298
312
  # Utility function to get a application's icon URL.
299
- # @return [String, nil] the URL to the icon image (nil if no image is set).
313
+ # @return [String, nil] the URL of the icon image (nil if no image is set).
300
314
  def icon_url
301
315
  return nil if @icon_id.nil?
302
316
  API.app_icon_url(@id, @icon_id)
@@ -313,7 +327,7 @@ module Discordrb
313
327
  # @return [Time] when this member joined the server.
314
328
  attr_reader :joined_at
315
329
 
316
- # @return [String, nil] the nickname this member has, or nil if it has none.
330
+ # @return [String, nil] the nickname this member has, or `nil` if it has none.
317
331
  attr_reader :nick
318
332
  alias_method :nickname, :nick
319
333
 
@@ -348,7 +362,7 @@ module Discordrb
348
362
  defined_permission?(action, channel)
349
363
  end
350
364
 
351
- # Checks whether this user has a particular permission defined (i. e. not implicit, through for example
365
+ # Checks whether this user has a particular permission defined (i.e. not implicit, through for example
352
366
  # Manage Roles)
353
367
  # @param action [Symbol] The permission that should be checked. See also {Permissions::Flags} for a list.
354
368
  # @param channel [Channel, nil] If channel overrides should be checked too, this channel specifies where the overrides should be checked.
@@ -380,10 +394,12 @@ module Discordrb
380
394
  private
381
395
 
382
396
  def defined_role_permission?(action, channel)
397
+ roles_to_check = [@server.everyone_role] + @roles
398
+
383
399
  # For each role, check if
384
400
  # (1) the channel explicitly allows or permits an action for the role and
385
401
  # (2) if the user is allowed to do the action if the channel doesn't specify
386
- @roles.reduce(false) do |can_act, role|
402
+ roles_to_check.reduce(false) do |can_act, role|
387
403
  # Get the override defined for the role on the channel
388
404
  channel_allow = permission_overwrite(action, channel, role.id)
389
405
  can_act = if channel_allow
@@ -453,7 +469,47 @@ module Discordrb
453
469
  end
454
470
  end
455
471
 
456
- # A presence represents a
472
+ # Voice regions are the locations of servers that handle voice communication in Discord
473
+ class VoiceRegion
474
+ # @return [String] unique ID for the region
475
+ attr_reader :id
476
+ alias_method :to_s, :id
477
+
478
+ # @return [String] name of the region
479
+ attr_reader :name
480
+
481
+ # @return [String] an example hostname for the region
482
+ attr_reader :sample_hostname
483
+
484
+ # @return [Integer] an example port for the region
485
+ attr_reader :sample_port
486
+
487
+ # @return [true, false] if this is a VIP-only server
488
+ attr_reader :vip
489
+
490
+ # @return [true, false] if this voice server is the closest to the client
491
+ attr_reader :optimal
492
+
493
+ # @return [true, false] whether this is a deprecated voice region (avoid switching to these)
494
+ attr_reader :deprecated
495
+
496
+ # @return [true, false] whether this is a custom voice region (used for events/etc)
497
+ attr_reader :custom
498
+
499
+ def initialize(data)
500
+ @id = data['id']
501
+
502
+ @name = data['name']
503
+
504
+ @sample_hostname = data['sample_hostname']
505
+ @sample_port = data['sample_port']
506
+
507
+ @vip = data['vip']
508
+ @optimal = data['optimal']
509
+ @deprecated = data['deprecated']
510
+ @custom = data['custom']
511
+ end
512
+ end
457
513
 
458
514
  # A member is a user on a server. It differs from regular users in that it has roles, voice statuses and things like
459
515
  # that.
@@ -520,48 +576,92 @@ module Discordrb
520
576
  @roles.any? { |e| e.id == role }
521
577
  end
522
578
 
579
+ # @see Member#set_roles
580
+ def roles=(role)
581
+ set_roles(role)
582
+ end
583
+
523
584
  # Bulk sets a member's roles.
524
585
  # @param role [Role, Array<Role>] The role(s) to set.
525
- def roles=(role)
586
+ # @param reason [String] The reason the user's roles are being changed.
587
+ def set_roles(role, reason = nil)
526
588
  role_ids = role_id_array(role)
527
- API::Server.update_member(@bot.token, @server.id, @user.id, roles: role_ids)
589
+ API::Server.update_member(@bot.token, @server.id, @user.id, roles: role_ids, reason: reason)
528
590
  end
529
591
 
530
592
  # Adds and removes roles from a member.
531
593
  # @param add [Role, Array<Role>] The role(s) to add.
532
594
  # @param remove [Role, Array<Role>] The role(s) to remove.
595
+ # @param reason [String] The reason the user's roles are being changed.
533
596
  # @example Remove the 'Member' role from a user, and add the 'Muted' role to them.
534
597
  # to_add = server.roles.find {|role| role.name == 'Muted'}
535
598
  # to_remove = server.roles.find {|role| role.name == 'Member'}
536
599
  # member.modify_roles(to_add, to_remove)
537
- def modify_roles(add, remove)
600
+ def modify_roles(add, remove, reason = nil)
538
601
  add_role_ids = role_id_array(add)
539
602
  remove_role_ids = role_id_array(remove)
540
603
  old_role_ids = @roles.map(&:id)
541
604
  new_role_ids = (old_role_ids - remove_role_ids + add_role_ids).uniq
542
605
 
543
- API::Server.update_member(@bot.token, @server.id, @user.id, roles: new_role_ids)
606
+ API::Server.update_member(@bot.token, @server.id, @user.id, roles: new_role_ids, reason: reason)
544
607
  end
545
608
 
546
609
  # Adds one or more roles to this member.
547
- # @param role [Role, Array<Role>] The role(s) to add.
548
- def add_role(role)
610
+ # @param role [Role, Array<Role, #resolve_id>, #resolve_id] The role(s) to add.
611
+ # @param reason [String] The reason the user's roles are being changed.
612
+ def add_role(role, reason = nil)
549
613
  role_ids = role_id_array(role)
550
- old_role_ids = @roles.map(&:id)
551
- new_role_ids = (old_role_ids + role_ids).uniq
552
614
 
553
- API::Server.update_member(@bot.token, @server.id, @user.id, roles: new_role_ids)
615
+ if role_ids.count == 1
616
+ API::Server.add_member_role(@bot.token, @server.id, @user.id, role_ids[0], reason)
617
+ else
618
+ old_role_ids = @roles.map(&:id)
619
+ new_role_ids = (old_role_ids + role_ids).uniq
620
+ API::Server.update_member(@bot.token, @server.id, @user.id, roles: new_role_ids, reason: reason)
621
+ end
554
622
  end
555
623
 
556
624
  # Removes one or more roles from this member.
557
625
  # @param role [Role, Array<Role>] The role(s) to remove.
558
- def remove_role(role)
559
- old_role_ids = @roles.map(&:id)
626
+ # @param reason [String] The reason the user's roles are being changed.
627
+ def remove_role(role, reason = nil)
560
628
  role_ids = role_id_array(role)
561
- new_role_ids = old_role_ids.reject { |i| role_ids.include?(i) }
562
629
 
563
- API::Server.update_member(@bot.token, @server.id, @user.id, roles: new_role_ids)
630
+ if role_ids.count == 1
631
+ API::Server.remove_member_role(@bot.token, @server.id, @user.id, role_ids[0], reason)
632
+ else
633
+ old_role_ids = @roles.map(&:id)
634
+ new_role_ids = old_role_ids.reject { |i| role_ids.include?(i) }
635
+ API::Server.update_member(@bot.token, @server.id, @user.id, roles: new_role_ids, reason: reason)
636
+ end
637
+ end
638
+
639
+ # @return [Role] the highest role this member has.
640
+ def highest_role
641
+ @roles.sort_by(&:position).last
642
+ end
643
+
644
+ # @return [Role, nil] the role this member is being hoisted with.
645
+ def hoist_role
646
+ hoisted_roles = @roles.select(&:hoist)
647
+ return nil if hoisted_roles.empty?
648
+ hoisted_roles.sort_by(&:position).last
649
+ end
650
+
651
+ # @return [Role, nil] the role this member is basing their colour on.
652
+ def colour_role
653
+ coloured_roles = @roles.select { |v| v.colour.combined.nonzero? }
654
+ return nil if coloured_roles.empty?
655
+ coloured_roles.sort_by(&:position).last
564
656
  end
657
+ alias_method :color_role, :colour_role
658
+
659
+ # @return [ColourRGB, nil] the colour this member has.
660
+ def colour
661
+ return nil unless colour_role
662
+ colour_role.color
663
+ end
664
+ alias_method :color, :colour
565
665
 
566
666
  # Server deafens this member.
567
667
  def server_deafen
@@ -583,21 +683,29 @@ module Discordrb
583
683
  API::Server.update_member(@bot.token, @server.id, @user.id, mute: false)
584
684
  end
585
685
 
686
+ # @see Member#set_nick
687
+ def nick=(nick)
688
+ set_nick(nick)
689
+ end
690
+
691
+ alias_method :nickname=, :nick=
692
+
586
693
  # Sets or resets this member's nickname. Requires the Change Nickname permission for the bot itself and Manage
587
694
  # Nicknames for other users.
588
695
  # @param nick [String, nil] The string to set the nickname to, or nil if it should be reset.
589
- def nick=(nick)
696
+ # @param reason [String] The reason the user's nickname is being changed.
697
+ def set_nick(nick, reason = nil)
590
698
  # Discord uses the empty string to signify 'no nickname' so we convert nil into that
591
699
  nick ||= ''
592
700
 
593
701
  if @user.current_bot?
594
- API::User.change_own_nickname(@bot.token, @server.id, nick)
702
+ API::User.change_own_nickname(@bot.token, @server.id, nick, reason)
595
703
  else
596
- API::Server.update_member(@bot.token, @server.id, @user.id, nick: nick)
704
+ API::Server.update_member(@bot.token, @server.id, @user.id, nick: nick, reason: nil)
597
705
  end
598
706
  end
599
707
 
600
- alias_method :nickname=, :nick=
708
+ alias_method :set_nickname, :set_nick
601
709
 
602
710
  # @return [String] the name the user displays as (nickname if they have one, username otherwise)
603
711
  def display_name
@@ -607,9 +715,13 @@ module Discordrb
607
715
  # Update this member's roles
608
716
  # @note For internal use only.
609
717
  # @!visibility private
610
- def update_roles(roles)
611
- @roles = roles.map do |role|
612
- role.is_a?(Role) ? role : @server.role(role.to_i)
718
+ def update_roles(role_ids)
719
+ @roles = []
720
+ role_ids.each do |id|
721
+ # It is posible for members to have roles that do not exist
722
+ # on the server any longer. See https://github.com/meew0/discordrb/issues/371
723
+ role = @server.role(id)
724
+ @roles << role if role
613
725
  end
614
726
  end
615
727
 
@@ -699,7 +811,7 @@ module Discordrb
699
811
 
700
812
  # Changes the bot's avatar.
701
813
  # @param avatar [String, #read] A JPG file to be used as the avatar, either
702
- # something readable (e. g. File Object) or as a data URL.
814
+ # something readable (e.g. File Object) or as a data URL.
703
815
  def avatar=(avatar)
704
816
  if avatar.respond_to? :read
705
817
  # Set the file to binary mode if supported, so we don't get problems with Windows
@@ -778,9 +890,16 @@ module Discordrb
778
890
  # @return [String] this role's name ("new role" if it hasn't been changed)
779
891
  attr_reader :name
780
892
 
893
+ # @return [Server] the server this role belongs to
894
+ attr_reader :server
895
+
781
896
  # @return [true, false] whether or not this role should be displayed separately from other users
782
897
  attr_reader :hoist
783
898
 
899
+ # @return [true, false] whether or not this role is managed by an integration or a bot
900
+ attr_reader :managed
901
+ alias_method :managed?, :managed
902
+
784
903
  # @return [true, false] whether this role can be mentioned using a role mention
785
904
  attr_reader :mentionable
786
905
  alias_method :mentionable?, :mentionable
@@ -824,6 +943,7 @@ module Discordrb
824
943
 
825
944
  @hoist = data['hoist']
826
945
  @mentionable = data['mentionable']
946
+ @managed = data['managed']
827
947
 
828
948
  @colour = ColourRGB.new(data['color'])
829
949
  end
@@ -836,7 +956,7 @@ module Discordrb
836
956
  # @return [Array<Member>] an array of members who have this role.
837
957
  # @note This requests a member chunk if it hasn't for the server before, which may be slow initially
838
958
  def members
839
- @server.members.select { |m| m.role? role }
959
+ @server.members.select { |m| m.role? self }
840
960
  end
841
961
 
842
962
  alias_method :users, :members
@@ -850,6 +970,7 @@ module Discordrb
850
970
  @hoist = other.hoist
851
971
  @colour = other.colour
852
972
  @position = other.position
973
+ @managed = other.managed
853
974
  end
854
975
 
855
976
  # Updates the data cache from a hash containing data
@@ -903,9 +1024,29 @@ module Discordrb
903
1024
  @permissions.bits = packed if update_perms
904
1025
  end
905
1026
 
1027
+ # Moves this role above another role in the list.
1028
+ # @param other [Role, #resolve_id, nil] The role above which this role should be moved. If it is `nil`,
1029
+ # the role will be moved above the @everyone role.
1030
+ # @return [Integer] the new position of this role
1031
+ def sort_above(other = nil)
1032
+ other = @server.role(other.resolve_id) if other
1033
+ roles = @server.roles.sort_by(&:position)
1034
+ roles.delete_at(@position)
1035
+
1036
+ index = other ? roles.index { |role| role.id == other.id } + 1 : 1
1037
+ roles.insert(index, self)
1038
+
1039
+ updated_roles = roles.map.with_index { |role, position| { id: role.id, position: position } }
1040
+ @server.update_role_positions(updated_roles)
1041
+ index
1042
+ end
1043
+
1044
+ alias_method :move_above, :sort_above
1045
+
906
1046
  # Deletes this role. This cannot be undone without recreating the role!
907
- def delete
908
- API::Server.delete_role(@bot.token, @server.id, @id)
1047
+ # @param reason [String] the reason for this role's deletion
1048
+ def delete(reason = nil)
1049
+ API::Server.delete_role(@bot.token, @server.id, @id, reason)
909
1050
  @server.delete_role(@id)
910
1051
  end
911
1052
 
@@ -984,7 +1125,7 @@ module Discordrb
984
1125
  attr_reader :inviter
985
1126
  alias_method :user, :inviter
986
1127
 
987
- # @return [true, false] whether or not this invite is temporary.
1128
+ # @return [true, false] whether or not this invite grants temporary membership. If someone joins a server with this invite, they will be removed from the server when they go offline unless they've received a role.
988
1129
  attr_reader :temporary
989
1130
  alias_method :temporary?, :temporary
990
1131
 
@@ -995,6 +1136,20 @@ module Discordrb
995
1136
  # @return [String] this invite's code
996
1137
  attr_reader :code
997
1138
 
1139
+ # @return [Integer, nil] the amount of members in the server. Will be nil if it has not been resolved.
1140
+ attr_reader :member_count
1141
+ alias_method :user_count, :member_count
1142
+
1143
+ # @return [Integer, nil] the amount of online members in the server. Will be nil if it has not been resolved.
1144
+ attr_reader :online_member_count
1145
+ alias_method :online_user_count, :online_member_count
1146
+
1147
+ # @return [Integer, nil] the invites max age before it expires, or nil if it's unknown. If the max age is 0, the invite will never expire unless it's deleted.
1148
+ attr_reader :max_age
1149
+
1150
+ # @return [Time, nil] when this invite was created, or nil if it's unknown
1151
+ attr_reader :created_at
1152
+
998
1153
  # @!visibility private
999
1154
  def initialize(data, bot)
1000
1155
  @bot = bot
@@ -1005,6 +1160,10 @@ module Discordrb
1005
1160
  @inviter = data['inviter'] ? (@bot.user(data['inviter']['id'].to_i) || User.new(data['inviter'], bot)) : nil
1006
1161
  @temporary = data['temporary']
1007
1162
  @revoked = data['revoked']
1163
+ @online_member_count = data['approximate_presence_count']
1164
+ @member_count = data['approximate_member_count']
1165
+ @max_age = data['max_age']
1166
+ @created_at = data['created_at']
1008
1167
 
1009
1168
  @code = data['code']
1010
1169
  end
@@ -1015,15 +1174,16 @@ module Discordrb
1015
1174
  end
1016
1175
 
1017
1176
  # Deletes this invite
1018
- def delete
1019
- API::Invite.delete(@bot.token, @code)
1177
+ # @param reason [String] The reason the invite is being deleted.
1178
+ def delete(reason = nil)
1179
+ API::Invite.delete(@bot.token, @code, reason)
1020
1180
  end
1021
1181
 
1022
1182
  alias_method :revoke, :delete
1023
1183
 
1024
1184
  # The inspect method is overwritten to give more useful output
1025
1185
  def inspect
1026
- "<Invite code=#{@code} channel=#{@channel} uses=#{@uses} temporary=#{@temporary} revoked=#{@revoked}>"
1186
+ "<Invite code=#{@code} channel=#{@channel} uses=#{@uses} temporary=#{@temporary} revoked=#{@revoked} created_at=#{@created_at} max_age=#{@max_age}>"
1027
1187
  end
1028
1188
 
1029
1189
  # Creates an invite URL.
@@ -1032,16 +1192,126 @@ module Discordrb
1032
1192
  end
1033
1193
  end
1034
1194
 
1195
+ # A permissions overwrite, when applied to channels describes additional
1196
+ # permissions a member needs to perform certain actions in context.
1197
+ class Overwrite
1198
+ # @return [Integer] id of the thing associated with this overwrite type
1199
+ attr_accessor :id
1200
+
1201
+ # @return [Symbol] either :role or :member
1202
+ attr_accessor :type
1203
+
1204
+ # @return [Permissions] allowed permissions for this overwrite type
1205
+ attr_accessor :allow
1206
+
1207
+ # @return [Permissions] denied permissions for this overwrite type
1208
+ attr_accessor :deny
1209
+
1210
+ # Creates a new Overwrite object
1211
+ # @example Create an overwrite for a role that can mention everyone, send TTS messages, but can't create instant invites
1212
+ # allow = Discordrb::Permissions.new
1213
+ # allow.can_mention_everyone = true
1214
+ # allow.can_send_tts_messages = true
1215
+ #
1216
+ # deny = Discordrb::Permissions.new
1217
+ # deny.can_create_instant_invite = true
1218
+ #
1219
+ # # Find some role by name
1220
+ # role = server.roles.find { |r| r.name == 'some role' }
1221
+ #
1222
+ # Overwrite.new(role, allow: allow, deny: deny)
1223
+ # @example Create an overwrite by ID and permissions bits
1224
+ # Overwrite.new(120571255635181568, type: 'member', allow: 1024, deny: 0)
1225
+ # @param object [Integer, #id] the ID or object this overwrite is for
1226
+ # @param type [String] the type of object this overwrite is for (only required if object is an Integer)
1227
+ # @param allow [Integer, Permissions] allowed permissions for this overwrite, by bits or a Permissions object
1228
+ # @param deny [Integer, Permissions] denied permissions for this overwrite, by bits or a Permissions object
1229
+ # @raise [ArgumentError] if type is not :member or :role
1230
+ def initialize(object = nil, type: nil, allow: 0, deny: 0)
1231
+ if type
1232
+ type = type.to_sym
1233
+ raise ArgumentError, 'Overwrite type must be :member or :role' unless (type != :member) || (type != :role)
1234
+ end
1235
+
1236
+ @id = object.respond_to?(:id) ? object.id : object
1237
+
1238
+ @type = if object.is_a?(User) || object.is_a?(Member) || object.is_a?(Recipient) || object.is_a?(Profile)
1239
+ :member
1240
+ elsif object.is_a? Role
1241
+ :role
1242
+ else
1243
+ type
1244
+ end
1245
+
1246
+ @allow = allow.is_a?(Permissions) ? allow : Permissions.new(allow)
1247
+ @deny = deny.is_a?(Permissions) ? deny : Permissions.new(deny)
1248
+ end
1249
+
1250
+ # Comparison by attributes [:id, :type, :allow, :deny]
1251
+ def ==(other)
1252
+ false unless other.is_a? Discordrb::Overwrite
1253
+ id == other.id &&
1254
+ type == other.type &&
1255
+ allow == other.allow &&
1256
+ deny == other.deny
1257
+ end
1258
+
1259
+ # @return [Overwrite] create an overwrite from a hash payload
1260
+ # @!visibility private
1261
+ def self.from_hash(data)
1262
+ new(
1263
+ data['id'].to_i,
1264
+ type: data['type'],
1265
+ allow: Permissions.new(data['allow']),
1266
+ deny: Permissions.new(data['deny'])
1267
+ )
1268
+ end
1269
+
1270
+ # @return [Overwrite] copies an overwrite from another Overwrite
1271
+ # @!visibility private
1272
+ def self.from_other(other)
1273
+ new(
1274
+ other.id,
1275
+ type: other.type,
1276
+ allow: Permissions.new(other.allow.bits),
1277
+ deny: Permissions.new(other.deny.bits)
1278
+ )
1279
+ end
1280
+
1281
+ # @return [Hash] hash representation of an overwrite
1282
+ # @!visibility private
1283
+ def to_hash
1284
+ {
1285
+ id: id,
1286
+ type: type,
1287
+ allow: allow.bits,
1288
+ deny: deny.bits
1289
+ }
1290
+ end
1291
+ end
1292
+
1035
1293
  # A Discord channel, including data like the topic
1036
1294
  class Channel
1037
1295
  include IDObject
1038
1296
 
1297
+ # Map of channel types
1298
+ TYPES = {
1299
+ text: 0,
1300
+ dm: 1,
1301
+ voice: 2,
1302
+ group: 3,
1303
+ category: 4
1304
+ }.freeze
1305
+
1039
1306
  # @return [String] this channel's name.
1040
1307
  attr_reader :name
1041
1308
 
1042
1309
  # @return [Server, nil] the server this channel is on. If this channel is a PM channel, it will be nil.
1043
1310
  attr_reader :server
1044
1311
 
1312
+ # @return [Integer, nil] the ID of the parent channel, if this channel is inside a cateogry
1313
+ attr_reader :parent_id
1314
+
1045
1315
  # @return [Integer] the type of this channel (0: text, 1: private, 2: voice, 3: group)
1046
1316
  attr_reader :type
1047
1317
 
@@ -1064,11 +1334,13 @@ module Discordrb
1064
1334
  # @return [Integer] the channel's position on the channel list
1065
1335
  attr_reader :position
1066
1336
 
1067
- # This channel's permission overwrites, represented as a hash of role/user ID to an OpenStruct which has the
1068
- # `allow` and `deny` properties which are {Permissions} objects respectively.
1069
- # @return [Hash<Integer => OpenStruct>] the channel's permission overwrites
1070
- attr_reader :permission_overwrites
1071
- alias_method :overwrites, :permission_overwrites
1337
+ # @return [true, false] if this channel is marked as nsfw
1338
+ attr_reader :nsfw
1339
+ alias_method :nsfw?, :nsfw
1340
+
1341
+ # @return [Integer] the amount of time (in seconds) users need to wait to send in between messages.
1342
+ attr_reader :rate_limit_per_user
1343
+ alias_method :slowmode_rate, :rate_limit_per_user
1072
1344
 
1073
1345
  # @return [true, false] whether or not this channel is a PM or group channel.
1074
1346
  def private?
@@ -1088,7 +1360,7 @@ module Discordrb
1088
1360
  # @!visibility private
1089
1361
  def initialize(data, bot, server = nil)
1090
1362
  @bot = bot
1091
- # data is a sometimes a Hash and other times an array of Hashes, you only want the last one if it's an array
1363
+ # data is sometimes a Hash and other times an array of Hashes, you only want the last one if it's an array
1092
1364
  data = data[-1] if data.is_a?(Array)
1093
1365
 
1094
1366
  @id = data['id'].to_i
@@ -1097,6 +1369,7 @@ module Discordrb
1097
1369
  @bitrate = data['bitrate']
1098
1370
  @user_limit = data['user_limit']
1099
1371
  @position = data['position']
1372
+ @parent_id = data['parent_id'].to_i if data['parent_id']
1100
1373
 
1101
1374
  if private?
1102
1375
  @recipients = []
@@ -1121,17 +1394,10 @@ module Discordrb
1121
1394
  end
1122
1395
  end
1123
1396
 
1124
- # Populate permission overwrites
1125
- @permission_overwrites = {}
1126
- return unless data['permission_overwrites']
1127
- data['permission_overwrites'].each do |element|
1128
- role_id = element['id'].to_i
1129
- deny = Permissions.new(element['deny'])
1130
- allow = Permissions.new(element['allow'])
1131
- @permission_overwrites[role_id] = OpenStruct.new
1132
- @permission_overwrites[role_id].deny = deny
1133
- @permission_overwrites[role_id].allow = allow
1134
- end
1397
+ @nsfw = data['nsfw'] || false || @name.start_with?('nsfw')
1398
+ @rate_limit_per_user = data['rate_limit_per_user'] || 0
1399
+
1400
+ process_permission_overwrites(data['permission_overwrites'])
1135
1401
  end
1136
1402
 
1137
1403
  # @return [true, false] whether or not this channel is a text channel
@@ -1154,6 +1420,194 @@ module Discordrb
1154
1420
  @type == 3
1155
1421
  end
1156
1422
 
1423
+ # @return [true, false]
1424
+ def category?
1425
+ @type == 4
1426
+ end
1427
+
1428
+ # @return [Channel, nil] the category channel, if this channel is in a category
1429
+ def category
1430
+ @bot.channel(@parent_id) if @parent_id
1431
+ end
1432
+
1433
+ alias_method :parent, :category
1434
+
1435
+ # Sets this channels parent category
1436
+ # @param channel [Channel, #resolve_id] the target category channel
1437
+ # @raise [ArgumentError] if the target channel isn't a category
1438
+ def category=(channel)
1439
+ channel = @bot.channel(channel)
1440
+ raise ArgumentError, 'Cannot set parent category to a channel that isn\'t a category' unless channel.category?
1441
+ update_channel_data(parent_id: channel.id)
1442
+ end
1443
+
1444
+ alias_method :parent=, :category=
1445
+
1446
+ # Sorts this channel's position to follow another channel.
1447
+ # @param other [Channel, #resolve_id, nil] The channel below which this channel should be sorted. If the given
1448
+ # channel is a category, this channel will be sorted at the top of that category. If it is `nil`, the channel will
1449
+ # be sorted at the top of the channel list.
1450
+ # @param lock_permissions [true, false] Whether the channel's permissions should be synced to the category's
1451
+ def sort_after(other = nil, lock_permissions = false)
1452
+ raise TypeError, 'other must be one of Channel, NilClass, or #resolve_id' unless other.is_a?(Channel) || other.nil? || other.respond_to?(:resolve_id)
1453
+ other = @bot.channel(other.resolve_id) if other
1454
+
1455
+ # Container for the API request payload
1456
+ move_argument = []
1457
+
1458
+ if other
1459
+ raise ArgumentError, 'Can only sort a channel after a channel of the same type!' unless other.category? || (@type == other.type)
1460
+
1461
+ raise ArgumentError, 'Can only sort a channel after a channel in the same server!' unless other.server == server
1462
+
1463
+ # Store `others` parent (or if `other` is a category itself)
1464
+ parent = if category? && other.category?
1465
+ # If we're sorting two categories, there is no new parent
1466
+ nil
1467
+ elsif other.category?
1468
+ # `other` is the category this channel will be moved into
1469
+ other
1470
+ else
1471
+ # `other`'s parent is the category this channel will be
1472
+ # moved into (if it exists)
1473
+ other.parent
1474
+ end
1475
+ end
1476
+
1477
+ # Collect and sort the IDs within the context (category or not) that we
1478
+ # need to form our payload with
1479
+ ids = if parent
1480
+ parent.children
1481
+ else
1482
+ @server.channels.reject(&:parent_id).select { |c| c.type == @type }
1483
+ end.sort_by(&:position).map(&:id)
1484
+
1485
+ # Move our channel ID after the target ID by deleting it,
1486
+ # getting the index of `other`, and inserting it after.
1487
+ ids.delete(@id) if ids.include?(@id)
1488
+ index = other ? (ids.index { |c| c == other.id } || -1) + 1 : 0
1489
+ ids.insert(index, @id)
1490
+
1491
+ # Generate `move_argument`, making the positions in order from how
1492
+ # we have sorted them in the above logic
1493
+ ids.each_with_index do |id, pos|
1494
+ # These keys are present in each element
1495
+ hash = { id: id, position: pos }
1496
+
1497
+ # Conditionally add `lock_permissions` and `parent_id` if we're
1498
+ # iterating past ourself
1499
+ if id == @id
1500
+ hash[:lock_permissions] = true if lock_permissions
1501
+ hash[:parent_id] = parent.nil? ? nil : parent.id
1502
+ end
1503
+
1504
+ # Add it to the stack
1505
+ move_argument << hash
1506
+ end
1507
+
1508
+ API::Server.update_channel_positions(@bot.token, @server.id, move_argument)
1509
+ end
1510
+
1511
+ # Sets whether this channel is NSFW
1512
+ # @param nsfw [true, false]
1513
+ # @raise [ArguementError] if value isn't one of true, false
1514
+ def nsfw=(nsfw)
1515
+ raise ArgumentError, 'nsfw value must be true or false' unless nsfw.is_a?(TrueClass) || nsfw.is_a?(FalseClass)
1516
+ update_channel_data(nsfw: nsfw)
1517
+ end
1518
+
1519
+ # This channel's permission overwrites
1520
+ # @overload permission_overwrites
1521
+ # The overwrites represented as a hash of role/user ID
1522
+ # to an Overwrite object
1523
+ # @return [Hash<Integer => Overwrite>] the channel's permission overwrites
1524
+ # @overload permission_overwrites(type)
1525
+ # Return an array of a certain type of overwrite
1526
+ # @param type [Symbol] the kind of overwrite to return
1527
+ # @return [Array<Overwrite>]
1528
+ def permission_overwrites(type = nil)
1529
+ return @permission_overwrites unless type
1530
+ @permission_overwrites.values.select { |e| e.type == type }
1531
+ end
1532
+
1533
+ alias_method :overwrites, :permission_overwrites
1534
+
1535
+ # Bulk sets this channels permission overwrites
1536
+ # @param overwrites [Array<Overwrite>]
1537
+ def permission_overwrites=(overwrites)
1538
+ update_channel_data(permission_overwrites: overwrites)
1539
+ end
1540
+
1541
+ # Sets the amount of time (in seconds) users have to wait in between sending messages.
1542
+ # @param rate [Integer]
1543
+ # @raise [ArgumentError] if value isn't between 0 and 120
1544
+ def rate_limit_per_user=(rate)
1545
+ raise ArgumentError, 'rate_limit_per_user must be between 0 and 120' unless rate.between?(0, 120)
1546
+ update_channel_data(rate_limit_per_user: rate)
1547
+ end
1548
+
1549
+ alias_method :slowmode_rate=, :rate_limit_per_user=
1550
+
1551
+ # Syncs this channels overwrites with its parent category
1552
+ # @raise [RuntimeError] if this channel is not in a category
1553
+ def sync_overwrites
1554
+ raise 'Cannot sync overwrites on a channel with no parent category' unless parent
1555
+ self.permission_overwrites = parent.permission_overwrites
1556
+ end
1557
+
1558
+ alias_method :sync, :sync_overwrites
1559
+
1560
+ # @return [true, false, nil] whether this channels permissions match the permission overwrites of the category that it's in, or nil if it is not in a category
1561
+ def synchronized?
1562
+ return unless parent
1563
+ permission_overwrites == parent.permission_overwrites
1564
+ end
1565
+
1566
+ alias_method :synced?, :synchronized?
1567
+
1568
+ # Returns the children of this channel, if it is a category. Otherwise returns an empty array.
1569
+ # @return [Array<Channel>]
1570
+ def children
1571
+ return [] unless category?
1572
+ server.channels.select { |c| c.parent_id == id }
1573
+ end
1574
+
1575
+ alias_method :channels, :children
1576
+
1577
+ # Returns the text channels in this category, if it is a category channel. Otherwise returns an empty array.
1578
+ # @return [Array<Channel>]
1579
+ def text_channels
1580
+ children.select(&:text?)
1581
+ end
1582
+
1583
+ # Returns the voice channels in this category, if it is a category channel. Otherwise returns an empty array.
1584
+ # @return [Array<Channel>]
1585
+ def voice_channels
1586
+ children.select(&:voice?)
1587
+ end
1588
+
1589
+ # @return [Overwrite] any member-type permission overwrites on this channel
1590
+ def member_overwrites
1591
+ permission_overwrites :member
1592
+ end
1593
+
1594
+ # @return [Overwrite] any role-type permission overwrites on this channel
1595
+ def role_overwrites
1596
+ permission_overwrites :role
1597
+ end
1598
+
1599
+ # @return [true, false] whether or not this channel is the default channel
1600
+ def default_channel?
1601
+ server.default_channel == self
1602
+ end
1603
+
1604
+ alias_method :default?, :default_channel?
1605
+
1606
+ # @return [true, false] whether or not this channel has slowmode enabled
1607
+ def slowmode?
1608
+ @rate_limit_per_user != 0
1609
+ end
1610
+
1157
1611
  # Sends a message to this channel.
1158
1612
  # @param content [String] The content to send. Should not be longer than 2000 characters or it will result in an error.
1159
1613
  # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
@@ -1207,6 +1661,8 @@ module Discordrb
1207
1661
  # @param file [File] The file to send. There's no clear size limit for this, you'll have to attempt it for yourself (most non-image files are fine, large images may fail to embed)
1208
1662
  # @param caption [string] The caption for the file.
1209
1663
  # @param tts [true, false] Whether or not this file's caption should be sent using Discord text-to-speech.
1664
+ # @example Send a file from disk
1665
+ # channel.send_file(File.open('rubytaco.png', 'r'))
1210
1666
  def send_file(file, caption: nil, tts: false)
1211
1667
  @bot.send_file(@id, file, caption: caption, tts: tts)
1212
1668
  end
@@ -1218,39 +1674,36 @@ module Discordrb
1218
1674
  end
1219
1675
 
1220
1676
  # Permanently deletes this channel
1221
- def delete
1222
- API::Channel.delete(@bot.token, @id)
1677
+ # @param reason [String] The reason the for the channel deletion.
1678
+ def delete(reason = nil)
1679
+ API::Channel.delete(@bot.token, @id, reason)
1223
1680
  end
1224
1681
 
1225
1682
  # Sets this channel's name. The name must be alphanumeric with dashes, unless this is a voice channel (then there are no limitations)
1226
1683
  # @param name [String] The new name.
1227
1684
  def name=(name)
1228
- @name = name
1229
- update_channel_data
1685
+ update_channel_data(name: name)
1230
1686
  end
1231
1687
 
1232
1688
  # Sets this channel's topic.
1233
1689
  # @param topic [String] The new topic.
1234
1690
  def topic=(topic)
1235
1691
  raise 'Tried to set topic on voice channel' if voice?
1236
- @topic = topic
1237
- update_channel_data
1692
+ update_channel_data(topic: topic)
1238
1693
  end
1239
1694
 
1240
1695
  # Sets this channel's bitrate.
1241
1696
  # @param bitrate [Integer] The new bitrate (in bps). Number has to be between 8000-96000 (128000 for VIP servers)
1242
1697
  def bitrate=(bitrate)
1243
1698
  raise 'Tried to set bitrate on text channel' if text?
1244
- @bitrate = bitrate
1245
- update_channel_data
1699
+ update_channel_data(bitrate: bitrate)
1246
1700
  end
1247
1701
 
1248
1702
  # Sets this channel's user limit.
1249
1703
  # @param limit [Integer] The new user limit. `0` for unlimited, has to be a number between 0-99
1250
1704
  def user_limit=(limit)
1251
1705
  raise 'Tried to set user_limit on text channel' if text?
1252
- @user_limit = limit
1253
- update_channel_data
1706
+ update_channel_data(user_limit: limit)
1254
1707
  end
1255
1708
 
1256
1709
  alias_method :limit=, :user_limit=
@@ -1258,47 +1711,48 @@ module Discordrb
1258
1711
  # Sets this channel's position in the list.
1259
1712
  # @param position [Integer] The new position.
1260
1713
  def position=(position)
1261
- @position = position
1262
- update_channel_data
1714
+ update_channel_data(position: position)
1263
1715
  end
1264
1716
 
1265
1717
  # Defines a permission overwrite for this channel that sets the specified thing to the specified allow and deny
1266
1718
  # permission sets, or change an existing one.
1267
- # @param thing [User, Role] What to define an overwrite for.
1268
- # @param allow [#bits, Permissions, Integer] The permission sets that should receive an `allow` override (i. e. a
1269
- # green checkmark on Discord)
1270
- # @param deny [#bits, Permissions, Integer] The permission sets that should receive a `deny` override (i. e. a red
1271
- # cross on Discord)
1272
- # @example Define a permission overwrite for a user that can then mention everyone and use TTS, but not create any invites
1273
- # allow = Discordrb::Permissions.new
1274
- # allow.can_mention_everyone = true
1275
- # allow.can_send_tts_messages = true
1719
+ # @overload define_overwrite(overwrite)
1720
+ # @param thing [Overwrite] an Overwrite object to apply to this channel
1721
+ # @param reason [String] The reason the for defining the overwrite.
1722
+ # @overload define_overwrite(thing, allow, deny)
1723
+ # @param thing [User, Role] What to define an overwrite for.
1724
+ # @param allow [#bits, Permissions, Integer] The permission sets that should receive an `allow` override (i.e. a
1725
+ # green checkmark on Discord)
1726
+ # @param deny [#bits, Permissions, Integer] The permission sets that should receive a `deny` override (i.e. a red
1727
+ # cross on Discord)
1728
+ # @param reason [String] The reason the for defining the overwrite.
1729
+ # @example Define a permission overwrite for a user that can then mention everyone and use TTS, but not create any invites
1730
+ # allow = Discordrb::Permissions.new
1731
+ # allow.can_mention_everyone = true
1732
+ # allow.can_send_tts_messages = true
1276
1733
  #
1277
- # deny = Discordrb::Permissions.new
1278
- # deny.can_create_instant_invite = true
1734
+ # deny = Discordrb::Permissions.new
1735
+ # deny.can_create_instant_invite = true
1279
1736
  #
1280
- # channel.define_overwrite(user, allow, deny)
1281
- def define_overwrite(thing, allow, deny)
1282
- allow_bits = allow.respond_to?(:bits) ? allow.bits : allow
1283
- deny_bits = deny.respond_to?(:bits) ? deny.bits : deny
1737
+ # channel.define_overwrite(user, allow, deny)
1738
+ def define_overwrite(thing, allow = 0, deny = 0, reason: nil)
1739
+ unless thing.is_a? Overwrite
1740
+ allow_bits = allow.respond_to?(:bits) ? allow.bits : allow
1741
+ deny_bits = deny.respond_to?(:bits) ? deny.bits : deny
1284
1742
 
1285
- type = if thing.is_a?(User) || thing.is_a?(Member) || thing.is_a?(Recipient) || thing.is_a?(Profile)
1286
- :member
1287
- elsif thing.is_a? Role
1288
- :role
1289
- else
1290
- raise ArgumentError, '`thing` in define_overwrite needs to be a kind of User (User, Member, Recipient, Profile) or a Role!'
1291
- end
1743
+ thing = Overwrite.new thing, allow: allow_bits, deny: deny_bits
1744
+ end
1292
1745
 
1293
- API::Channel.update_permission(@bot.token, @id, thing.id, allow_bits, deny_bits, type)
1746
+ API::Channel.update_permission(@bot.token, @id, thing.id, thing.allow.bits, thing.deny.bits, thing.type, reason)
1294
1747
  end
1295
1748
 
1296
1749
  # Deletes a permission overwrite for this channel
1297
1750
  # @param target [Member, User, Role, Profile, Recipient, #resolve_id] What permission overwrite to delete
1298
- def delete_overwrite(target)
1751
+ # @param reason [String] The reason the for the overwrite deletion.
1752
+ def delete_overwrite(target, reason = nil)
1299
1753
  raise 'Tried deleting a overwrite for an invalid target' unless target.is_a?(Member) || target.is_a?(User) || target.is_a?(Role) || target.is_a?(Profile) || target.is_a?(Recipient) || target.respond_to?(:resolve_id)
1300
1754
 
1301
- API::Channel.delete_permission(@bot.token, @id, target.resolve_id)
1755
+ API::Channel.delete_permission(@bot.token, @id, target.resolve_id, reason)
1302
1756
  end
1303
1757
 
1304
1758
  # Updates the cached data from another channel.
@@ -1313,6 +1767,9 @@ module Discordrb
1313
1767
  @bitrate = other.bitrate
1314
1768
  @user_limit = other.user_limit
1315
1769
  @permission_overwrites = other.permission_overwrites
1770
+ @nsfw = other.nsfw
1771
+ @parent_id = other.parent_id
1772
+ @rate_limit_per_user = other.rate_limit_per_user
1316
1773
  end
1317
1774
 
1318
1775
  # The list of users currently in this channel. For a voice channel, it will return all the members currently
@@ -1333,25 +1790,28 @@ module Discordrb
1333
1790
  # start at the current message.
1334
1791
  # @param after_id [Integer] The ID of the oldest message the retrieval should start at, or nil if it should start
1335
1792
  # as soon as possible with the specified amount.
1793
+ # @param around_id [Integer] The ID of the message retrieval should start from, reading in both directions
1336
1794
  # @example Count the number of messages in the last 50 messages that contain the letter 'e'.
1337
1795
  # message_count = channel.history(50).count {|message| message.content.include? "e"}
1796
+ # @example Get the last 10 messages before the provided message.
1797
+ # last_ten_messages = channel.history(10, message.id)
1338
1798
  # @return [Array<Message>] the retrieved messages.
1339
- def history(amount, before_id = nil, after_id = nil)
1340
- logs = API::Channel.messages(@bot.token, @id, amount, before_id, after_id)
1799
+ def history(amount, before_id = nil, after_id = nil, around_id = nil)
1800
+ logs = API::Channel.messages(@bot.token, @id, amount, before_id, after_id, around_id)
1341
1801
  JSON.parse(logs).map { |message| Message.new(message, @bot) }
1342
1802
  end
1343
1803
 
1344
- # Retrieves message history, but only message IDs for use with prune
1804
+ # Retrieves message history, but only message IDs for use with prune.
1345
1805
  # @note For internal use only
1346
1806
  # @!visibility private
1347
- def history_ids(amount, before_id = nil, after_id = nil)
1348
- logs = API::Channel.messages(@bot.token, @id, amount, before_id, after_id)
1807
+ def history_ids(amount, before_id = nil, after_id = nil, around_id = nil)
1808
+ logs = API::Channel.messages(@bot.token, @id, amount, before_id, after_id, around_id)
1349
1809
  JSON.parse(logs).map { |message| message['id'].to_i }
1350
1810
  end
1351
1811
 
1352
1812
  # Returns a single message from this channel's history by ID.
1353
1813
  # @param message_id [Integer] The ID of the message to retrieve.
1354
- # @return [Message] the retrieved message.
1814
+ # @return [Message, nil] the retrieved message, or `nil` if it couldn't be found.
1355
1815
  def load_message(message_id)
1356
1816
  response = API::Channel.message(@bot.token, @id, message_id)
1357
1817
  return Message.new(JSON.parse(response), @bot)
@@ -1361,7 +1821,7 @@ module Discordrb
1361
1821
 
1362
1822
  alias_method :message, :load_message
1363
1823
 
1364
- # Requests all pinned messages of a channel.
1824
+ # Requests all pinned messages in a channel.
1365
1825
  # @return [Array<Message>] the received messages.
1366
1826
  def pins
1367
1827
  msgs = API::Channel.pinned_messages(@bot.token, @id)
@@ -1369,22 +1829,41 @@ module Discordrb
1369
1829
  end
1370
1830
 
1371
1831
  # Delete the last N messages on this channel.
1372
- # @param amount [Integer] How many messages to delete. Must be a value between 2 and 100 (Discord limitation)
1832
+ # @param amount [Integer] The amount of message history to consider for pruning. Must be a value between 2 and 100 (Discord limitation)
1373
1833
  # @param strict [true, false] Whether an error should be raised when a message is reached that is too old to be bulk
1374
1834
  # deleted. If this is false only a warning message will be output to the console.
1375
1835
  # @raise [ArgumentError] if the amount of messages is not a value between 2 and 100
1376
- def prune(amount, strict = false)
1377
- raise ArgumentError, 'Can only prune between 2 and 100 messages!' unless amount.between?(2, 100)
1836
+ # @yield [message] Yields each message in this channels history for filtering the messages to delete
1837
+ # @example Pruning messages from a specific user ID
1838
+ # channel.prune(100) { |m| m.author.id == 83283213010599936 }
1839
+ # @return [Integer] The amount of messages that were successfully deleted
1840
+ def prune(amount, strict = false, &block)
1841
+ raise ArgumentError, 'Can only delete between 1 and 100 messages!' unless amount.between?(1, 100)
1842
+
1843
+ messages =
1844
+ if block_given?
1845
+ history(amount).select(&block).map(&:id)
1846
+ else
1847
+ history_ids(amount)
1848
+ end
1378
1849
 
1379
- messages = history_ids(amount)
1380
- bulk_delete(messages, strict)
1850
+ case messages.size
1851
+ when 0
1852
+ 0
1853
+ when 1
1854
+ API::Channel.delete_message(@bot.token, @id, messages.first)
1855
+ 1
1856
+ else
1857
+ bulk_delete(messages, strict)
1858
+ end
1381
1859
  end
1382
1860
 
1383
1861
  # Deletes a collection of messages
1384
- # @param messages [Array<Message, Integer>] the messages (or message IDs) to delete. Total must be an amount between 2 and 100 (Discord limitation)
1862
+ # @param messages [Array<Message, Integer, #resolve_id>] the messages (or message IDs) to delete. Total must be an amount between 2 and 100 (Discord limitation)
1385
1863
  # @param strict [true, false] Whether an error should be raised when a message is reached that is too old to be bulk
1386
1864
  # deleted. If this is false only a warning message will be output to the console.
1387
1865
  # @raise [ArgumentError] if the amount of messages is not a value between 2 and 100
1866
+ # @return [Integer] The amount of messages that were successfully deleted
1388
1867
  def delete_messages(messages, strict = false)
1389
1868
  raise ArgumentError, 'Can only delete between 2 and 100 messages!' unless messages.count.between?(2, 100)
1390
1869
 
@@ -1402,17 +1881,27 @@ module Discordrb
1402
1881
  # Add an {Await} for a message in this channel. This is identical in functionality to adding a
1403
1882
  # {Discordrb::Events::MessageEvent} await with the `in` attribute as this channel.
1404
1883
  # @see Bot#add_await
1884
+ # @deprecated Will be changed to blocking behavior in v4.0. Use {#await!} instead.
1405
1885
  def await(key, attributes = {}, &block)
1406
1886
  @bot.add_await(key, Discordrb::Events::MessageEvent, { in: @id }.merge(attributes), &block)
1407
1887
  end
1408
1888
 
1889
+ # Add a blocking {Await} for a message in this channel. This is identical in functionality to adding a
1890
+ # {Discordrb::Events::MessageEvent} await with the `in` attribute as this channel.
1891
+ # @see Bot#add_await!
1892
+ def await!(attributes = {})
1893
+ @bot.add_await!(Discordrb::Events::MessageEvent, { in: @id }.merge(attributes))
1894
+ end
1895
+
1409
1896
  # Creates a new invite to this channel.
1410
1897
  # @param max_age [Integer] How many seconds this invite should last.
1411
1898
  # @param max_uses [Integer] How many times this invite should be able to be used.
1412
1899
  # @param temporary [true, false] Whether membership should be temporary (kicked after going offline).
1900
+ # @param unique [true, false] If true, Discord will always send a unique invite instead of possibly re-using a similar one
1901
+ # @param reason [String] The reason the for the creation of this invite.
1413
1902
  # @return [Invite] the created invite.
1414
- def make_invite(max_age = 0, max_uses = 0, temporary = false)
1415
- response = API::Channel.create_invite(@bot.token, @id, max_age, max_uses, temporary)
1903
+ def make_invite(max_age = 0, max_uses = 0, temporary = false, unique = false, reason = nil)
1904
+ response = API::Channel.create_invite(@bot.token, @id, max_age, max_uses, temporary, unique, reason)
1416
1905
  Invite.new(JSON.parse(response), @bot)
1417
1906
  end
1418
1907
 
@@ -1421,13 +1910,15 @@ module Discordrb
1421
1910
  # Starts typing, which displays the typing indicator on the client for five seconds.
1422
1911
  # If you want to keep typing you'll have to resend this every five seconds. (An abstraction
1423
1912
  # for this will eventually be coming)
1913
+ # @example Send a typing indicator for the bot in a given channel.
1914
+ # channel.start_typing()
1424
1915
  def start_typing
1425
1916
  API::Channel.start_typing(@bot.token, @id)
1426
1917
  end
1427
1918
 
1428
1919
  # Creates a Group channel
1429
1920
  # @param user_ids [Array<Integer>] Array of user IDs to add to the new group channel (Excluding
1430
- # the recipient of the PM channel).
1921
+ # the recipient of the PM channel).
1431
1922
  # @return [Channel] the created channel.
1432
1923
  def create_group(user_ids)
1433
1924
  raise 'Attempted to create group channel on a non-pm channel!' unless pm?
@@ -1436,7 +1927,7 @@ module Discordrb
1436
1927
  channel.add_group_users(user_ids)
1437
1928
  end
1438
1929
 
1439
- # Adds a user to a Group channel
1930
+ # Adds a user to a group channel.
1440
1931
  # @param user_ids [Array<#resolve_id>, #resolve_id] User ID or array of user IDs to add to the group channel.
1441
1932
  # @return [Channel] the group channel.
1442
1933
  def add_group_users(user_ids)
@@ -1464,7 +1955,7 @@ module Discordrb
1464
1955
 
1465
1956
  alias_method :remove_group_user, :remove_group_users
1466
1957
 
1467
- # Leaves the group
1958
+ # Leaves the group.
1468
1959
  def leave_group
1469
1960
  raise 'Attempted to leave a non-group channel!' unless group?
1470
1961
  API::Channel.leave_group(@bot.token, @id)
@@ -1472,7 +1963,23 @@ module Discordrb
1472
1963
 
1473
1964
  alias_method :leave, :leave_group
1474
1965
 
1475
- # The inspect method is overwritten to give more useful output
1966
+ # Requests a list of Webhooks on the channel.
1967
+ # @return [Array<Webhook>] webhooks on the channel.
1968
+ def webhooks
1969
+ raise 'Tried to request webhooks from a non-server channel' unless server
1970
+ webhooks = JSON.parse(API::Channel.webhooks(@bot.token, @id))
1971
+ webhooks.map { |webhook_data| Webhook.new(webhook_data, @bot) }
1972
+ end
1973
+
1974
+ # Requests a list of Invites to the channel.
1975
+ # @return [Array<Invite>] invites to the channel.
1976
+ def invites
1977
+ raise 'Tried to request invites from a non-server channel' unless server
1978
+ invites = JSON.parse(API::Channel.invites(@bot.token, @id))
1979
+ invites.map { |invite_data| Invite.new(invite_data, @bot) }
1980
+ end
1981
+
1982
+ # The default `inspect` method is overwritten to give more useful output.
1476
1983
  def inspect
1477
1984
  "<Channel name=#{@name} id=#{@id} topic=\"#{@topic}\" type=#{@type} position=#{@position} server=#{@server}>"
1478
1985
  end
@@ -1499,12 +2006,29 @@ module Discordrb
1499
2006
  @recipients.delete(recipient)
1500
2007
  end
1501
2008
 
2009
+ # Updates the cached data with new data
2010
+ # @note For internal use only
2011
+ # @!visibility private
2012
+ def update_data(new_data = nil)
2013
+ new_data ||= JSON.parse(API::Channel.resolve(@bot.token, @id))
2014
+ @name = new_data[:name] || new_data['name'] || @name
2015
+ @topic = new_data[:topic] || new_data['topic'] || @topic
2016
+ @position = new_data[:position] || new_data['position'] || @position
2017
+ @bitrate = new_data[:bitrate] || new_data['bitrate'] || @bitrate
2018
+ @user_limit = new_data[:user_limit] || new_data['user_limit'] || @user_limit
2019
+ new_nsfw = new_data.key?(:nsfw) ? new_data[:nsfw] : new_data['nsfw']
2020
+ @nsfw = new_nsfw.nil? ? @nsfw : new_nsfw
2021
+ @parent_id = new_data[:parent_id] || new_data['parent_id'] || @parent_id
2022
+ process_permission_overwrites(new_data[:permission_overwrites] || new_data['permission_overwrites'])
2023
+ @rate_limit_per_user = new_data[:rate_limit_per_user] || new_data['rate_limit_per_user'] || @rate_limit_per_user
2024
+ end
2025
+
1502
2026
  private
1503
2027
 
1504
2028
  # For bulk_delete checking
1505
2029
  TWO_WEEKS = 86_400 * 14
1506
2030
 
1507
- # Deletes a list of messages on this channel using bulk delete
2031
+ # Deletes a list of messages on this channel using bulk delete.
1508
2032
  def bulk_delete(ids, strict = false)
1509
2033
  min_snowflake = IDObject.synthesise(Time.now - TWO_WEEKS)
1510
2034
 
@@ -1514,14 +2038,38 @@ module Discordrb
1514
2038
  message = "Attempted to bulk_delete message #{e} which is too old (min = #{min_snowflake})"
1515
2039
  raise ArgumentError, message if strict
1516
2040
  Discordrb::LOGGER.warn(message)
1517
- false
2041
+ true
1518
2042
  end
1519
2043
 
1520
2044
  API::Channel.bulk_delete_messages(@bot.token, @id, ids)
1521
- end
1522
-
1523
- def update_channel_data
1524
- API::Channel.update(@bot.token, @id, @name, @topic, @position, @bitrate, @user_limit)
2045
+ ids.size
2046
+ end
2047
+
2048
+ def update_channel_data(new_data)
2049
+ new_nsfw = new_data[:nsfw].is_a?(TrueClass) || new_data[:nsfw].is_a?(FalseClass) ? new_nsfw : @nsfw
2050
+ # send permission_overwrite only when explicitly set
2051
+ overwrites = new_data[:permission_overwrites] ? new_data[:permission_overwrites].map { |_, v| v.to_hash } : nil
2052
+ response = JSON.parse(API::Channel.update(@bot.token, @id,
2053
+ new_data[:name] || @name,
2054
+ new_data[:topic] || @topic,
2055
+ new_data[:position] || @position,
2056
+ new_data[:bitrate] || @bitrate,
2057
+ new_data[:user_limit] || @user_limit,
2058
+ new_nsfw,
2059
+ overwrites,
2060
+ new_data[:parent_id] || @parent_id,
2061
+ new_data[:rate_limit_per_user] || @rate_limit_per_user))
2062
+ update_data(response)
2063
+ end
2064
+
2065
+ def process_permission_overwrites(overwrites)
2066
+ # Populate permission overwrites
2067
+ @permission_overwrites = {}
2068
+ return unless overwrites
2069
+ overwrites.each do |element|
2070
+ id = element['id'].to_i
2071
+ @permission_overwrites[id] = Overwrite.from_hash(element)
2072
+ end
1525
2073
  end
1526
2074
  end
1527
2075
 
@@ -1548,15 +2096,34 @@ module Discordrb
1548
2096
  # * `:image`
1549
2097
  attr_reader :type
1550
2098
 
1551
- # @return [EmbedProvider, nil] the provider of the embed object. `nil` is there is not a provider
2099
+ # @return [Time, nil] the timestamp of the embed object. `nil` if there is not a timestamp
2100
+ attr_reader :timestamp
2101
+
2102
+ # @return [String, nil] the color of the embed object. `nil` if there is not a color
2103
+ attr_reader :color
2104
+ alias_method :colour, :color
2105
+
2106
+ # @return [EmbedFooter, nil] the footer of the embed object. `nil` if there is not a footer
2107
+ attr_reader :footer
2108
+
2109
+ # @return [EmbedProvider, nil] the provider of the embed object. `nil` if there is not a provider
1552
2110
  attr_reader :provider
1553
2111
 
1554
- # @return [EmbedThumbnail, nil] the thumbnail of the embed object. `nil` is there is not a thumbnail
2112
+ # @return [EmbedImage, nil] the image of the embed object. `nil` if there is not an image
2113
+ attr_reader :image
2114
+
2115
+ # @return [EmbedThumbnail, nil] the thumbnail of the embed object. `nil` if there is not a thumbnail
1555
2116
  attr_reader :thumbnail
1556
2117
 
1557
- # @return [EmbedAuthor, nil] the author of the embed object. `nil` is there is not an author
2118
+ # @return [EmbedVideo, nil] the video of the embed object. `nil` if there is not a video
2119
+ attr_reader :video
2120
+
2121
+ # @return [EmbedAuthor, nil] the author of the embed object. `nil` if there is not an author
1558
2122
  attr_reader :author
1559
2123
 
2124
+ # @return [Array<EmbedField>, nil] the fields of the embed object. `nil` if there are no fields
2125
+ attr_reader :fields
2126
+
1560
2127
  # @!visibility private
1561
2128
  def initialize(data, message)
1562
2129
  @message = message
@@ -1565,9 +2132,91 @@ module Discordrb
1565
2132
  @title = data['title']
1566
2133
  @type = data['type'].to_sym
1567
2134
  @description = data['description']
2135
+ @timestamp = data['timestamp'].nil? ? nil : Time.parse(data['timestamp'])
2136
+ @color = data['color']
2137
+ @footer = data['footer'].nil? ? nil : EmbedFooter.new(data['footer'], self)
2138
+ @image = data['image'].nil? ? nil : EmbedImage.new(data['image'], self)
2139
+ @video = data['video'].nil? ? nil : EmbedVideo.new(data['video'], self)
1568
2140
  @provider = data['provider'].nil? ? nil : EmbedProvider.new(data['provider'], self)
1569
2141
  @thumbnail = data['thumbnail'].nil? ? nil : EmbedThumbnail.new(data['thumbnail'], self)
1570
2142
  @author = data['author'].nil? ? nil : EmbedAuthor.new(data['author'], self)
2143
+ @fields = data['fields'].nil? ? nil : data['fields'].map { |field| EmbedField.new(field, self) }
2144
+ end
2145
+ end
2146
+
2147
+ # An Embed footer for the embed object.
2148
+ class EmbedFooter
2149
+ # @return [Embed] the embed object this is based on.
2150
+ attr_reader :embed
2151
+
2152
+ # @return [String] the footer text.
2153
+ attr_reader :text
2154
+
2155
+ # @return [String] the URL of the footer icon.
2156
+ attr_reader :icon_url
2157
+
2158
+ # @return [String] the proxied URL of the footer icon.
2159
+ attr_reader :proxy_icon_url
2160
+
2161
+ # @!visibility private
2162
+ def initialize(data, embed)
2163
+ @embed = embed
2164
+
2165
+ @text = data['text']
2166
+ @icon_url = data['icon_url']
2167
+ @proxy_icon_url = data['proxy_icon_url']
2168
+ end
2169
+ end
2170
+
2171
+ # An Embed image for the embed object.
2172
+ class EmbedImage
2173
+ # @return [Embed] the embed object this is based on.
2174
+ attr_reader :embed
2175
+
2176
+ # @return [String] the source URL of the image.
2177
+ attr_reader :url
2178
+
2179
+ # @return [String] the proxy URL of the image.
2180
+ attr_reader :proxy_url
2181
+
2182
+ # @return [Integer] the width of the image, in pixels.
2183
+ attr_reader :width
2184
+
2185
+ # @return [Integer] the height of the image, in pixels.
2186
+ attr_reader :height
2187
+
2188
+ # @!visibility private
2189
+ def initialize(data, embed)
2190
+ @embed = embed
2191
+
2192
+ @url = data['url']
2193
+ @proxy_url = data['proxy_url']
2194
+ @width = data['width']
2195
+ @height = data['height']
2196
+ end
2197
+ end
2198
+
2199
+ # An Embed video for the embed object
2200
+ class EmbedVideo
2201
+ # @return [Embed] the embed object this is based on.
2202
+ attr_reader :embed
2203
+
2204
+ # @return [String] the source URL of the video.
2205
+ attr_reader :url
2206
+
2207
+ # @return [Integer] the width of the video, in pixels.
2208
+ attr_reader :width
2209
+
2210
+ # @return [Integer] the height of the video, in pixels.
2211
+ attr_reader :height
2212
+
2213
+ # @!visibility private
2214
+ def initialize(data, embed)
2215
+ @embed = embed
2216
+
2217
+ @url = data['url']
2218
+ @width = data['width']
2219
+ @height = data['height']
1571
2220
  end
1572
2221
  end
1573
2222
 
@@ -1580,7 +2229,7 @@ module Discordrb
1580
2229
  attr_reader :url
1581
2230
 
1582
2231
  # @return [String] the thumbnail's proxy URL - I'm not sure what exactly this does, but I think it has something to
1583
- # do with CDNs
2232
+ # do with CDNs.
1584
2233
  attr_reader :proxy_url
1585
2234
 
1586
2235
  # @return [Integer] the width of this thumbnail file, in pixels.
@@ -1608,7 +2257,7 @@ module Discordrb
1608
2257
  # @return [String] the provider's name.
1609
2258
  attr_reader :name
1610
2259
 
1611
- # @return [String, nil] the URL of the provider. `nil` is there is no URL
2260
+ # @return [String, nil] the URL of the provider, or `nil` if there is no URL.
1612
2261
  attr_reader :url
1613
2262
 
1614
2263
  # @!visibility private
@@ -1628,20 +2277,52 @@ module Discordrb
1628
2277
  # @return [String] the author's name.
1629
2278
  attr_reader :name
1630
2279
 
1631
- # @return [String, nil] the URL of the author's website. `nil` is there is no URL
2280
+ # @return [String, nil] the URL of the author's website, or `nil` if there is no URL.
1632
2281
  attr_reader :url
1633
2282
 
2283
+ # @return [String, nil] the icon of the author, or `nil` if there is no icon.
2284
+ attr_reader :icon_url
2285
+
2286
+ # @return [String, nil] the Discord proxy URL, or `nil` if there is no `icon_url`.
2287
+ attr_reader :proxy_icon_url
2288
+
1634
2289
  # @!visibility private
1635
2290
  def initialize(data, embed)
1636
2291
  @embed = embed
1637
2292
 
1638
2293
  @name = data['name']
1639
2294
  @url = data['url']
2295
+ @icon_url = data['icon_url']
2296
+ @proxy_icon_url = data['proxy_icon_url']
1640
2297
  end
1641
2298
  end
1642
2299
 
1643
- # An attachment to a message
1644
- class Attachment
2300
+ # An Embed field for the embed object
2301
+ class EmbedField
2302
+ # @return [Embed] the embed object this is based on.
2303
+ attr_reader :embed
2304
+
2305
+ # @return [String] the field's name.
2306
+ attr_reader :name
2307
+
2308
+ # @return [String] the field's value.
2309
+ attr_reader :value
2310
+
2311
+ # @return [true, false] whether this field is inline.
2312
+ attr_reader :inline
2313
+
2314
+ # @!visibility private
2315
+ def initialize(data, embed)
2316
+ @embed = embed
2317
+
2318
+ @name = data['name']
2319
+ @value = data['value']
2320
+ @inline = data['inline']
2321
+ end
2322
+ end
2323
+
2324
+ # An attachment to a message
2325
+ class Attachment
1645
2326
  include IDObject
1646
2327
 
1647
2328
  # @return [Message] the message this attachment belongs to.
@@ -1651,7 +2332,7 @@ module Discordrb
1651
2332
  attr_reader :url
1652
2333
 
1653
2334
  # @return [String] the attachment's proxy URL - I'm not sure what exactly this does, but I think it has something to
1654
- # do with CDNs
2335
+ # do with CDNs.
1655
2336
  attr_reader :proxy_url
1656
2337
 
1657
2338
  # @return [String] the attachment's filename.
@@ -1660,10 +2341,10 @@ module Discordrb
1660
2341
  # @return [Integer] the attachment's file size in bytes.
1661
2342
  attr_reader :size
1662
2343
 
1663
- # @return [Integer, nil] the width of an image file, in pixels, or nil if the file is not an image.
2344
+ # @return [Integer, nil] the width of an image file, in pixels, or `nil` if the file is not an image.
1664
2345
  attr_reader :width
1665
2346
 
1666
- # @return [Integer, nil] the height of an image file, in pixels, or nil if the file is not an image.
2347
+ # @return [Integer, nil] the height of an image file, in pixels, or `nil` if the file is not an image.
1667
2348
  attr_reader :height
1668
2349
 
1669
2350
  # @!visibility private
@@ -1671,6 +2352,7 @@ module Discordrb
1671
2352
  @bot = bot
1672
2353
  @message = message
1673
2354
 
2355
+ @id = data['id'].to_i
1674
2356
  @url = data['url']
1675
2357
  @proxy_url = data['proxy_url']
1676
2358
  @filename = data['filename']
@@ -1724,14 +2406,14 @@ module Discordrb
1724
2406
  # @return [Array<Embed>] the embed objects contained in this message.
1725
2407
  attr_reader :embeds
1726
2408
 
1727
- # @return [Hash<String, Reaction>] the reaction objects attached to this message keyed by the name of the reaction
2409
+ # @return [Hash<String => Reaction>] the reaction objects attached to this message keyed by the name of the reaction
1728
2410
  attr_reader :reactions
1729
2411
 
1730
2412
  # @return [true, false] whether the message used Text-To-Speech (TTS) or not.
1731
2413
  attr_reader :tts
1732
2414
  alias_method :tts?, :tts
1733
2415
 
1734
- # @return [String] used for validating a message was sent
2416
+ # @return [String] used for validating a message was sent.
1735
2417
  attr_reader :nonce
1736
2418
 
1737
2419
  # @return [true, false] whether the message was edited or not.
@@ -1747,7 +2429,7 @@ module Discordrb
1747
2429
  attr_reader :pinned
1748
2430
  alias_method :pinned?, :pinned
1749
2431
 
1750
- # @return [Integer, nil] the webhook ID that sent this message, or nil if it wasn't sent through a webhook.
2432
+ # @return [Integer, nil] the webhook ID that sent this message, or `nil` if it wasn't sent through a webhook.
1751
2433
  attr_reader :webhook_id
1752
2434
 
1753
2435
  # The discriminator that webhook user accounts have.
@@ -1778,7 +2460,7 @@ module Discordrb
1778
2460
 
1779
2461
  unless member
1780
2462
  Discordrb::LOGGER.debug("Member with ID #{data['author']['id']} not cached (possibly left the server).")
1781
- member = @bot.user(data['author']['id'].to_i)
2463
+ member = @bot.ensure_user(data['author'])
1782
2464
  end
1783
2465
 
1784
2466
  member
@@ -1837,7 +2519,7 @@ module Discordrb
1837
2519
  # Edits this message to have the specified content instead.
1838
2520
  # You can only edit your own messages.
1839
2521
  # @param new_content [String] the new content the message should have.
1840
- # @param new_embed [Hash, Discordrb::Webhooks::Embed, nil] The new embed the message should have. If nil the message will be changed to have no embed.
2522
+ # @param new_embed [Hash, Discordrb::Webhooks::Embed, nil] The new embed the message should have. If `nil` the message will be changed to have no embed.
1841
2523
  # @return [Message] the resulting message.
1842
2524
  def edit(new_content, new_embed = nil)
1843
2525
  response = API::Channel.edit_message(@bot.token, @channel.id, @id, new_content, [], new_embed ? new_embed.to_hash : nil)
@@ -1866,10 +2548,17 @@ module Discordrb
1866
2548
 
1867
2549
  # Add an {Await} for a message with the same user and channel.
1868
2550
  # @see Bot#add_await
2551
+ # @deprecated Will be changed to blocking behavior in v4.0. Use {#await!} instead.
1869
2552
  def await(key, attributes = {}, &block)
1870
2553
  @bot.add_await(key, Discordrb::Events::MessageEvent, { from: @author.id, in: @channel.id }.merge(attributes), &block)
1871
2554
  end
1872
2555
 
2556
+ # Add a blocking {Await} for a message with the same user and channel.
2557
+ # @see Bot#add_await!
2558
+ def await!(attributes = {})
2559
+ @bot.add_await!(Discordrb::Events::MessageEvent, { from: @author.id, in: @channel.id }.merge(attributes))
2560
+ end
2561
+
1873
2562
  # @return [true, false] whether this message was sent by the current {Bot}.
1874
2563
  def from_bot?
1875
2564
  @author && @author.current_bot?
@@ -1884,11 +2573,11 @@ module Discordrb
1884
2573
  # @return [Array<String>] the emoji mentions found in the message
1885
2574
  def scan_for_emoji
1886
2575
  emoji = @content.split
1887
- emoji = emoji.grep(/<:(?<name>\w+):(?<id>\d+)>?/)
2576
+ emoji = emoji.grep(/<(?<animated>a)?:(?<name>\w+):(?<id>\d+)>?/)
1888
2577
  emoji
1889
2578
  end
1890
2579
 
1891
- # @return [Array<Emoji>] the emotes that were used/mentioned in this message (Only returns Emoji the bot has access to, else nil).
2580
+ # @return [Array<Emoji>] the emotes that were used/mentioned in this message.
1892
2581
  def emoji
1893
2582
  return if @content.nil?
1894
2583
 
@@ -1899,27 +2588,27 @@ module Discordrb
1899
2588
  @emoji
1900
2589
  end
1901
2590
 
1902
- # Check if any emoji got used in this message
1903
- # @return [true, false] whether or not any emoji got used
2591
+ # Check if any emoji were used in this message.
2592
+ # @return [true, false] whether or not any emoji were used
1904
2593
  def emoji?
1905
2594
  emoji = scan_for_emoji
1906
2595
  return true unless emoji.empty?
1907
2596
  end
1908
2597
 
1909
- # Check if any reactions got used in this message
2598
+ # Check if any reactions were used in this message.
1910
2599
  # @return [true, false] whether or not this message has reactions
1911
2600
  def reactions?
1912
2601
  @reactions.any?
1913
2602
  end
1914
2603
 
1915
- # Returns the reactions made by the current bot or user
2604
+ # Returns the reactions made by the current bot or user.
1916
2605
  # @return [Array<Reaction>] the reactions
1917
2606
  def my_reactions
1918
- @reactions.select(&:me)
2607
+ @reactions.values.select(&:me)
1919
2608
  end
1920
2609
 
1921
- # Reacts to a message
1922
- # @param [String, #to_reaction] the unicode emoji, Emoji, or GlobalEmoji
2610
+ # Reacts to a message.
2611
+ # @param reaction [String, #to_reaction] the unicode emoji or {Emoji}
1923
2612
  def create_reaction(reaction)
1924
2613
  reaction = reaction.to_reaction if reaction.respond_to?(:to_reaction)
1925
2614
  API::Channel.create_reaction(@bot.token, @channel.id, @id, reaction)
@@ -1928,8 +2617,10 @@ module Discordrb
1928
2617
 
1929
2618
  alias_method :react, :create_reaction
1930
2619
 
1931
- # Returns the list of users who reacted with a certain reaction
1932
- # @param [String, #to_reaction] the unicode emoji, Emoji, or GlobalEmoji
2620
+ # Returns the list of users who reacted with a certain reaction.
2621
+ # @param reaction [String, #to_reaction] the unicode emoji or {Emoji}
2622
+ # @example Get all the users that reacted with a thumbsup.
2623
+ # thumbs_up_reactions = message.reacted_with("\u{1F44D}")
1933
2624
  # @return [Array<User>] the users who used this reaction
1934
2625
  def reacted_with(reaction)
1935
2626
  reaction = reaction.to_reaction if reaction.respond_to?(:to_reaction)
@@ -1937,22 +2628,22 @@ module Discordrb
1937
2628
  response.map { |d| User.new(d, @bot) }
1938
2629
  end
1939
2630
 
1940
- # Deletes a reaction made by a user on this message
1941
- # @param [User, #resolve_id] the user who used this reaction
1942
- # @param [String, #to_reaction] the reaction to remove
2631
+ # Deletes a reaction made by a user on this message.
2632
+ # @param user [User, #resolve_id] the user who used this reaction
2633
+ # @param reaction [String, #to_reaction] the reaction to remove
1943
2634
  def delete_reaction(user, reaction)
1944
2635
  reaction = reaction.to_reaction if reaction.respond_to?(:to_reaction)
1945
2636
  API::Channel.delete_user_reaction(@bot.token, @channel.id, @id, reaction, user.resolve_id)
1946
2637
  end
1947
2638
 
1948
- # Delete's this clients reaction on this message
1949
- # @param [String, #to_reaction] the reaction to remove
2639
+ # Deletes this client's reaction on this message.
2640
+ # @param reaction [String, #to_reaction] the reaction to remove
1950
2641
  def delete_own_reaction(reaction)
1951
2642
  reaction = reaction.to_reaction if reaction.respond_to?(:to_reaction)
1952
2643
  API::Channel.delete_own_reaction(@bot.token, @channel.id, @id, reaction)
1953
2644
  end
1954
2645
 
1955
- # Removes all reactions from this message
2646
+ # Removes all reactions from this message.
1956
2647
  def delete_all_reactions
1957
2648
  API::Channel.delete_all_reactions(@bot.token, @channel.id, @id)
1958
2649
  end
@@ -1963,7 +2654,7 @@ module Discordrb
1963
2654
  end
1964
2655
  end
1965
2656
 
1966
- # A reaction to a message
2657
+ # A reaction to a message.
1967
2658
  class Reaction
1968
2659
  # @return [Integer] the amount of users who have reacted with this reaction
1969
2660
  attr_reader :count
@@ -1984,6 +2675,13 @@ module Discordrb
1984
2675
  @id = data['emoji']['id'].nil? ? nil : data['emoji']['id'].to_i
1985
2676
  @name = data['emoji']['name']
1986
2677
  end
2678
+
2679
+ # Converts this Reaction into a string that can be sent back to Discord in other reaction endpoints.
2680
+ # If ID is present, it will be rendered into the form of `name:id`.
2681
+ # @return [String] the name of this reaction, including the ID if it is a custom emoji
2682
+ def to_s
2683
+ id.nil? ? name : "#{name}:#{id}"
2684
+ end
1987
2685
  end
1988
2686
 
1989
2687
  # Server emoji
@@ -1999,6 +2697,10 @@ module Discordrb
1999
2697
  # @return [Array<Role>] roles this emoji is active for
2000
2698
  attr_reader :roles
2001
2699
 
2700
+ # @return [true, false] if the emoji is animated
2701
+ attr_reader :animated
2702
+ alias_method :animated?, :animated
2703
+
2002
2704
  def initialize(data, bot, server)
2003
2705
  @bot = bot
2004
2706
  @roles = nil
@@ -2006,13 +2708,14 @@ module Discordrb
2006
2708
  @name = data['name']
2007
2709
  @server = server
2008
2710
  @id = data['id'].nil? ? nil : data['id'].to_i
2711
+ @animated = data['animated']
2009
2712
 
2010
2713
  process_roles(data['roles']) if server
2011
2714
  end
2012
2715
 
2013
2716
  # @return [String] the layout to mention it (or have it used) in a message
2014
2717
  def mention
2015
- "<:#{@name}:#{@id}>"
2718
+ "<#{'a' if animated}:#{name}:#{id}>"
2016
2719
  end
2017
2720
 
2018
2721
  alias_method :use, :mention
@@ -2020,17 +2723,17 @@ module Discordrb
2020
2723
 
2021
2724
  # @return [String] the layout to use this emoji in a reaction
2022
2725
  def to_reaction
2023
- "#{@name}:#{@id}"
2726
+ "#{name}:#{id}"
2024
2727
  end
2025
2728
 
2026
2729
  # @return [String] the icon URL of the emoji
2027
2730
  def icon_url
2028
- API.emoji_icon_url(@id)
2731
+ API.emoji_icon_url(id)
2029
2732
  end
2030
2733
 
2031
2734
  # The inspect method is overwritten to give more useful output
2032
2735
  def inspect
2033
- "<Emoji name=#{@name} id=#{@id}>"
2736
+ "<Emoji name=#{name} id=#{id} animated=#{animated}>"
2034
2737
  end
2035
2738
 
2036
2739
  # @!visibility private
@@ -2044,61 +2747,6 @@ module Discordrb
2044
2747
  end
2045
2748
  end
2046
2749
 
2047
- # Emoji that is not tailored to a server
2048
- class GlobalEmoji
2049
- include IDObject
2050
-
2051
- # @return [String] the emoji name
2052
- attr_reader :name
2053
-
2054
- # @return [Hash<Integer => Array<Role>>] roles this emoji is active for in every server
2055
- attr_reader :role_associations
2056
-
2057
- def initialize(data, bot)
2058
- @bot = bot
2059
- @roles = nil
2060
-
2061
- @name = data.name
2062
- @id = data.id
2063
- @role_associations = Hash.new([])
2064
- @role_associations[data.server.id] = data.roles
2065
- end
2066
-
2067
- # @return [String] the layout to mention it (or have it used) in a message
2068
- def mention
2069
- "<:#{@name}:#{@id}>"
2070
- end
2071
-
2072
- alias_method :use, :mention
2073
- alias_method :to_s, :mention
2074
-
2075
- # @return [String] the layout to use this emoji in a reaction
2076
- def to_reaction
2077
- "#{@name}:#{@id}"
2078
- end
2079
-
2080
- # @return [String] the icon URL of the emoji
2081
- def icon_url
2082
- API.emoji_icon_url(@id)
2083
- end
2084
-
2085
- # The inspect method is overwritten to give more useful output
2086
- def inspect
2087
- "<GlobalEmoji name=#{@name} id=#{@id}>"
2088
- end
2089
-
2090
- # @!visibility private
2091
- def process_roles(roles)
2092
- new_roles = []
2093
- return unless roles
2094
- roles.each do
2095
- role = server.role(role_id)
2096
- new_roles << role
2097
- end
2098
- new_roles
2099
- end
2100
- end
2101
-
2102
2750
  # Basic attributes a server should have
2103
2751
  module ServerAttributes
2104
2752
  # @return [String] this server's name.
@@ -2149,7 +2797,7 @@ module Discordrb
2149
2797
  attr_reader :emoticon
2150
2798
  alias_method :emoticon?, :emoticon
2151
2799
 
2152
- # @return [String] the integration type (Youtube, Twitch, etc.)
2800
+ # @return [String] the integration type (YouTube, Twitch, etc.)
2153
2801
  attr_reader :type
2154
2802
 
2155
2803
  # @return [true, false] whether the integration is enabled
@@ -2182,7 +2830,7 @@ module Discordrb
2182
2830
  @type = data['type']
2183
2831
  @account = IntegrationAccount.new(data['account'])
2184
2832
  @synced_at = Time.parse(data['synced_at'])
2185
- @expire_behaviour = [:remove, :kick][data['expire_behavior']]
2833
+ @expire_behaviour = %i[remove kick][data['expire_behavior']]
2186
2834
  @expire_grace_period = data['expire_grace_period']
2187
2835
  @user = @bot.ensure_user(data['user'])
2188
2836
  @role = server.role(data['role_id']) || nil
@@ -2200,8 +2848,8 @@ module Discordrb
2200
2848
  include IDObject
2201
2849
  include ServerAttributes
2202
2850
 
2203
- # @return [String] the region the server is on (e. g. `amsterdam`).
2204
- attr_reader :region
2851
+ # @return [String] the ID of the region the server is on (e.g. `amsterdam`).
2852
+ attr_reader :region_id
2205
2853
 
2206
2854
  # @return [Member] The server owner.
2207
2855
  attr_reader :owner
@@ -2212,7 +2860,7 @@ module Discordrb
2212
2860
  # @return [Array<Role>] an array of all the roles created on this server.
2213
2861
  attr_reader :roles
2214
2862
 
2215
- # @return [Hash<Integer, Emoji>] a hash of all the emoji available on this server.
2863
+ # @return [Hash<Integer => Emoji>] a hash of all the emoji available on this server.
2216
2864
  attr_reader :emoji
2217
2865
  alias_method :emojis, :emoji
2218
2866
 
@@ -2227,15 +2875,9 @@ module Discordrb
2227
2875
  # @return [Integer] the absolute number of members on this server, offline or not.
2228
2876
  attr_reader :member_count
2229
2877
 
2230
- # @return [Symbol] the verification level of the server (:none = none, :low = 'Must have a verified email on their Discord account', :medium = 'Has to be registered with Discord for at least 5 minutes', :high = 'Has to be a member of this server for at least 10 minutes').
2231
- attr_reader :verification_level
2232
-
2233
2878
  # @return [Integer] the amount of time after which a voice user gets moved into the AFK channel, in seconds.
2234
2879
  attr_reader :afk_timeout
2235
2880
 
2236
- # @return [Channel, nil] the AFK voice channel of this server, or nil if none is set
2237
- attr_reader :afk_channel
2238
-
2239
2881
  # @return [Hash<Integer => VoiceState>] the hash (user ID => voice state) of voice states of members on this server
2240
2882
  attr_reader :voice_states
2241
2883
 
@@ -2244,13 +2886,13 @@ module Discordrb
2244
2886
  @bot = bot
2245
2887
  @owner_id = data['owner_id'].to_i
2246
2888
  @id = data['id'].to_i
2889
+
2890
+ process_channels(data['channels'])
2247
2891
  update_data(data)
2248
2892
 
2249
2893
  @large = data['large']
2250
2894
  @member_count = data['member_count']
2251
- @verification_level = [:none, :low, :medium, :high][data['verification_level']]
2252
2895
  @splash_id = nil
2253
- @embed = nil
2254
2896
  @features = data['features'].map { |element| element.downcase.to_sym }
2255
2897
  @members = {}
2256
2898
  @voice_states = {}
@@ -2260,24 +2902,38 @@ module Discordrb
2260
2902
  process_emoji(data['emojis'])
2261
2903
  process_members(data['members'])
2262
2904
  process_presences(data['presences'])
2263
- process_channels(data['channels'])
2264
2905
  process_voice_states(data['voice_states'])
2265
2906
 
2266
2907
  # Whether this server's members have been chunked (resolved using op 8 and GUILD_MEMBERS_CHUNK) yet
2267
2908
  @chunked = false
2268
2909
  @processed_chunk_members = 0
2269
2910
 
2270
- # Only get the owner of the server actually exists (i. e. not for ServerDeleteEvent)
2911
+ # Only get the owner of the server actually exists (i.e. not for ServerDeleteEvent)
2271
2912
  @owner = member(@owner_id) if exists
2272
2913
  end
2273
2914
 
2274
- # @return [Channel] The default channel on this server (usually called #general)
2275
- def default_channel
2276
- @bot.channel(@id)
2915
+ # The default channel is the text channel on this server with the highest position
2916
+ # that the bot has Read Messages permission on.
2917
+ # @param send_messages [true, false] whether to additionally consider if the bot has Send Messages permission
2918
+ # @return [Channel, nil] The default channel on this server, or `nil` if there are no channels that the bot can read.
2919
+ def default_channel(send_messages = false)
2920
+ bot_member = member(@bot.profile)
2921
+ text_channels.sort_by { |e| [e.position, e.id] }.find do |e|
2922
+ if send_messages
2923
+ bot_member.can_read_messages?(e) && bot_member.can_send_messages?(e)
2924
+ else
2925
+ bot_member.can_read_messages?(e)
2926
+ end
2927
+ end
2277
2928
  end
2278
2929
 
2279
2930
  alias_method :general_channel, :default_channel
2280
2931
 
2932
+ # @return [Role] The @everyone role on this server
2933
+ def everyone_role
2934
+ role(@id)
2935
+ end
2936
+
2281
2937
  # Gets a role on this server based on its ID.
2282
2938
  # @param id [Integer, String, #resolve_id] The role ID to look for.
2283
2939
  def role(id)
@@ -2294,7 +2950,7 @@ module Discordrb
2294
2950
  return nil unless request
2295
2951
 
2296
2952
  member = @bot.member(self, id)
2297
- @members[id] = member
2953
+ @members[id] = member unless member.nil?
2298
2954
  rescue
2299
2955
  nil
2300
2956
  end
@@ -2317,19 +2973,92 @@ module Discordrb
2317
2973
  integration.map { |element| Integration.new(element, @bot, self) }
2318
2974
  end
2319
2975
 
2976
+ # @param action [Symbol] The action to only include.
2977
+ # @param user [User, #resolve_id] The user to filter entries to.
2978
+ # @param limit [Integer] The amount of entries to limit it to.
2979
+ # @param before [Entry, #resolve_id] The entry to use to not include all entries after it.
2980
+ # @return [AuditLogs] The server's audit logs.
2981
+ def audit_logs(action: nil, user: nil, limit: 50, before: nil)
2982
+ raise 'Invalid audit log action!' if action && AuditLogs::Actions.key(action).nil?
2983
+ action = AuditLogs::Actions.key(action)
2984
+ user = user.resolve_id if user
2985
+ before = before.resolve_id if before
2986
+ AuditLogs.new(self, @bot, JSON.parse(API::Server.audit_logs(@bot.token, @id, limit, user, action, before)))
2987
+ end
2988
+
2320
2989
  # Cache @embed
2321
2990
  # @note For internal use only
2322
2991
  # @!visibility private
2323
- def cache_embed
2324
- @embed ||= JSON.parse(API::Server.resolve(@bot.token, @id))['embed_enabled']
2992
+ def cache_embed_data
2993
+ data = JSON.parse(API::Server.embed(@bot.token, @id))
2994
+ @embed_enabled = data['enabled']
2995
+ @embed_channel_id = data['channel_id']
2325
2996
  end
2326
2997
 
2327
2998
  # @return [true, false] whether or not the server has widget enabled
2328
- def embed?
2329
- cache_embed if @embed.nil?
2330
- @embed
2999
+ def embed_enabled?
3000
+ cache_embed_data if @embed_enabled.nil?
3001
+ @embed_enabled
3002
+ end
3003
+ alias_method :widget_enabled, :embed_enabled?
3004
+ alias_method :widget?, :embed_enabled?
3005
+ alias_method :embed?, :embed_enabled?
3006
+
3007
+ # @return [Channel, nil] the channel the server embed will make an invite for.
3008
+ def embed_channel
3009
+ cache_embed_data if @embed_enabled.nil?
3010
+ @bot.channel(@embed_channel_id) if @embed_channel_id
3011
+ end
3012
+ alias_method :widget_channel, :embed_channel
3013
+
3014
+ # Sets whether this server's embed (widget) is enabled
3015
+ # @param value [true, false]
3016
+ def embed_enabled=(value)
3017
+ modify_embed(value, embed_channel)
2331
3018
  end
2332
3019
 
3020
+ alias_method :widget_enabled=, :embed_enabled=
3021
+
3022
+ # Sets whether this server's embed (widget) is enabled
3023
+ # @param value [true, false]
3024
+ # @param reason [String, nil] the reason to be shown in the audit log for this action
3025
+ def set_embed_enabled(value, reason = nil)
3026
+ modify_embed(value, embed_channel, reason)
3027
+ end
3028
+
3029
+ alias_method :set_widget_enabled, :set_embed_enabled
3030
+
3031
+ # Changes the channel on the server's embed (widget)
3032
+ # @param channel [Channel, String, Integer, #resolve_id] the channel to be referenced by the embed
3033
+ def embed_channel=(channel)
3034
+ modify_embed(embed?, channel)
3035
+ end
3036
+
3037
+ alias_method :widget_channel=, :embed_channel=
3038
+
3039
+ # Changes the channel on the server's embed (widget)
3040
+ # @param channel [Channel, String, Integer, #resolve_id] the channel to be referenced by the embed
3041
+ # @param reason [String, nil] the reason to be shown in the audit log for this action
3042
+ def set_embed_channel(channel, reason = nil)
3043
+ modify_embed(embed?, channel, reason)
3044
+ end
3045
+
3046
+ alias_method :set_widget_channel, :set_embed_channel
3047
+
3048
+ # Changes the channel on the server's embed (widget), and sets whether it is enabled.
3049
+ # @param enabled [true, false] whether the embed (widget) is enabled
3050
+ # @param channel [Channel, String, Integer, #resolve_id] the channel to be referenced by the embed
3051
+ # @param reason [String, nil] the reason to be shown in the audit log for this action
3052
+ def modify_embed(enabled, channel, reason = nil)
3053
+ cache_embed_data if @embed_enabled.nil?
3054
+ channel_id = channel ? channel.resolve_id : @embed_channel_id
3055
+ response = JSON.parse(API::Server.modify_embed(@bot.token, @id, enabled, channel_id, reason))
3056
+ @embed_enabled = response['enabled']
3057
+ @embed_channel_id = response['channel_id']
3058
+ end
3059
+
3060
+ alias_method :modify_widget, :modify_embed
3061
+
2333
3062
  # @param include_idle [true, false] Whether to count idle members as online.
2334
3063
  # @param include_bots [true, false] Whether to include bot accounts in the count.
2335
3064
  # @return [Array<Member>] an array of online members on this server.
@@ -2341,6 +3070,49 @@ module Discordrb
2341
3070
 
2342
3071
  alias_method :online_users, :online_members
2343
3072
 
3073
+ # Adds a member to this guild that has granted this bot's application an OAuth2 access token
3074
+ # with the `guilds.join` scope.
3075
+ # For more information about Discord's OAuth2 implementation, see: https://discordapp.com/developers/docs/topics/oauth2
3076
+ # @note Your bot must be present in this server, and have permission to create instant invites for this to work.
3077
+ # @param user [Integer, User, #resolve_id] the user, or ID of the user to add to this server
3078
+ # @param access_token [String] the OAuth2 Bearer token that has been granted the `guilds.join` scope
3079
+ # @param nick [String] the nickname to give this member upon joining
3080
+ # @param roles [Role, Array<Integer, Role, #resolve_id>] the role (or roles) to give this member upon joining
3081
+ # @param deaf [true, false] whether this member will be server deafened upon joining
3082
+ # @param mute [true, false] whether this member will be server muted upon joining
3083
+ # @return [Member] the created member
3084
+ def add_member_using_token(user, access_token, nick: nil, roles: [], deaf: false, mute: false)
3085
+ user_id = user.resolve_id
3086
+ roles = roles.is_a?(Array) ? roles.map(&:resolve_id) : [roles.resolve_id]
3087
+ response = JSON.parse(API::Server.add_member(@bot.token, @id, user_id, access_token, nick, roles, deaf, mute))
3088
+ add_member Member.new(response, self, @bot)
3089
+ end
3090
+
3091
+ # Returns the amount of members that are candidates for pruning
3092
+ # @param days [Integer] the number of days to consider for inactivity
3093
+ # @return [Integer] number of members to be removed
3094
+ # @raise [ArgumentError] if days is not between 1 and 30 (inclusive)
3095
+ def prune_count(days)
3096
+ raise ArgumentError, 'Days must be between 1 and 30' unless days.between?(1, 30)
3097
+
3098
+ response = JSON.parse API::Server.prune_count(@bot.token, @id, days)
3099
+ response['pruned']
3100
+ end
3101
+
3102
+ # Prunes (kicks) an amount of members for inactivity
3103
+ # @param days [Integer] the number of days to consider for inactivity (between 1 and 30)
3104
+ # @param reason [String] The reason the for the prune.
3105
+ # @return [Integer] the number of members removed at the end of the operation
3106
+ # @raise [ArgumentError] if days is not between 1 and 30 (inclusive)
3107
+ def begin_prune(days, reason = nil)
3108
+ raise ArgumentError, 'Days must be between 1 and 30' unless days.between?(1, 30)
3109
+
3110
+ response = JSON.parse API::Server.begin_prune(@bot.token, @id, days, reason)
3111
+ response['pruned']
3112
+ end
3113
+
3114
+ alias_method :prune, :begin_prune
3115
+
2344
3116
  # @return [Array<Channel>] an array of text channels on this server
2345
3117
  def text_channels
2346
3118
  @channels.select(&:text?)
@@ -2351,11 +3123,21 @@ module Discordrb
2351
3123
  @channels.select(&:voice?)
2352
3124
  end
2353
3125
 
3126
+ # @return [Array<Channel>] an array of category channels on this server
3127
+ def categories
3128
+ @channels.select(&:category?)
3129
+ end
3130
+
3131
+ # @return [Array<Channel>] an array of channels on this server that are not in a category
3132
+ def orphan_channels
3133
+ @channels.reject { |c| c.parent || c.category? }
3134
+ end
3135
+
2354
3136
  # @return [String, nil] the widget URL to the server that displays the amount of online members in a
2355
3137
  # stylish way. `nil` if the widget is not enabled.
2356
3138
  def widget_url
2357
- cache_embed if @embed.nil?
2358
- return nil unless @embed
3139
+ update_data if @embed_enabled.nil?
3140
+ return unless @embed_enabled
2359
3141
  API.widget_url(@id)
2360
3142
  end
2361
3143
 
@@ -2368,8 +3150,8 @@ module Discordrb
2368
3150
  # @return [String, nil] the widget banner URL to the server that displays the amount of online members,
2369
3151
  # server icon and server name in a stylish way. `nil` if the widget is not enabled.
2370
3152
  def widget_banner_url(style)
2371
- return nil unless @embed
2372
- cache_embed if @embed.nil?
3153
+ update_data if @embed_enabled.nil?
3154
+ return unless @embed_enabled
2373
3155
  API.widget_url(@id, style)
2374
3156
  end
2375
3157
 
@@ -2408,12 +3190,23 @@ module Discordrb
2408
3190
  end
2409
3191
  end
2410
3192
 
3193
+ # Updates the positions of all roles on the server
3194
+ # @note For internal use only
3195
+ # @!visibility private
3196
+ def update_role_positions(role_positions)
3197
+ response = JSON.parse(API::Server.update_role_positions(@bot.token, @id, role_positions))
3198
+ response.each do |data|
3199
+ updated_role = Role.new(data, @bot, self)
3200
+ role(updated_role.id).update_from(updated_role)
3201
+ end
3202
+ end
3203
+
2411
3204
  # Adds a member to the member cache.
2412
3205
  # @note For internal use only
2413
3206
  # @!visibility private
2414
3207
  def add_member(member)
2415
- @members[member.id] = member
2416
3208
  @member_count += 1
3209
+ @members[member.id] = member
2417
3210
  end
2418
3211
 
2419
3212
  # Removes a member from the member cache.
@@ -2466,50 +3259,89 @@ module Discordrb
2466
3259
  end
2467
3260
 
2468
3261
  # Creates a channel on this server with the given name.
3262
+ # @note If parent is provided, permission overwrites have the follow behavior:
3263
+ #
3264
+ # 1. If overwrites is null, the new channel inherits the parent's permissions.
3265
+ # 2. If overwrites is [], the new channel inherits the parent's permissions.
3266
+ # 3. If you supply one or more overwrites, the channel will be created with those permissions and ignore the parents.
3267
+ #
2469
3268
  # @param name [String] Name of the channel to create
2470
- # @param type [Integer] Type of channel to create (0: text, 2: voice)
3269
+ # @param type [Integer, Symbol] Type of channel to create (0: text, 2: voice, 4: category)
3270
+ # @param topic [String] the topic of this channel, if it will be a text channel
3271
+ # @param bitrate [Integer] the bitrate of this channel, if it will be a voice channel
3272
+ # @param user_limit [Integer] the user limit of this channel, if it will be a voice channel
3273
+ # @param permission_overwrites [Array<Hash>, Array<Overwrite>] permission overwrites for this channel
3274
+ # @param parent [Channel, #resolve_id] parent category for this channel to be created in.
3275
+ # @param nsfw [true, false] whether this channel should be created as nsfw
3276
+ # @param rate_limit_per_user [Integer] how many seconds users need to wait in between messages.
3277
+ # @param reason [String] The reason the for the creation of this channel.
2471
3278
  # @return [Channel] the created channel.
2472
- # @raise [ArgumentError] if type is not 0 or 2
2473
- def create_channel(name, type = 0)
2474
- raise ArgumentError, 'Channel type must be either 0 (text) or 2 (voice)!' unless [0, 2].include?(type)
2475
- response = API::Server.create_channel(@bot.token, @id, name, type)
3279
+ # @raise [ArgumentError] if type is not 0 (text), 2 (voice), or 4 (category)
3280
+ def create_channel(name, type = 0, topic: nil, bitrate: nil, user_limit: nil, permission_overwrites: nil, parent: nil, nsfw: false, rate_limit_per_user: nil, reason: nil)
3281
+ type = Channel::TYPES[type] if type.is_a?(Symbol)
3282
+ raise ArgumentError, 'Channel type must be either 0 (text), 2 (voice), or 4 (category)!' unless [0, 2, 4].include?(type)
3283
+ permission_overwrites.map! { |e| e.is_a?(Overwrite) ? e.to_hash : e } if permission_overwrites.is_a?(Array)
3284
+ parent_id = parent.respond_to?(:resolve_id) ? parent.resolve_id : nil
3285
+ response = API::Server.create_channel(@bot.token, @id, name, type, topic, bitrate, user_limit, permission_overwrites, parent_id, nsfw, rate_limit_per_user, reason)
2476
3286
  Channel.new(JSON.parse(response), @bot)
2477
3287
  end
2478
3288
 
2479
- # Creates a role on this server which can then be modified. It will be initialized (on Discord's side)
2480
- # with the regular role defaults the client uses, i. e. name is "new role", permissions are the default,
2481
- # colour is the default etc.
3289
+ # Creates a role on this server which can then be modified. It will be initialized
3290
+ # with the regular role defaults the client uses unless specified, i.e. name is "new role",
3291
+ # permissions are the default, colour is the default etc.
3292
+ # @param name [String] Name of the role to create
3293
+ # @param colour [Integer, ColourRGB, #combined] The roles colour
3294
+ # @param hoist [true, false]
3295
+ # @param mentionable [true, false]
3296
+ # @param permissions [Integer, Array<Symbol>, Permissions, #bits] The permissions to write to the new role.
3297
+ # @param reason [String] The reason the for the creation of this role.
2482
3298
  # @return [Role] the created role.
2483
- def create_role
2484
- response = API::Server.create_role(@bot.token, @id)
3299
+ def create_role(name: 'new role', colour: 0, hoist: false, mentionable: false, permissions: 104_324_161, reason: nil)
3300
+ colour = colour.respond_to?(:combined) ? colour.combined : colour
3301
+
3302
+ permissions = if permissions.is_a?(Array)
3303
+ Permissions.bits(permissions)
3304
+ elsif permissions.respond_to?(:bits)
3305
+ permissions.bits
3306
+ else
3307
+ permissions
3308
+ end
3309
+
3310
+ response = API::Server.create_role(@bot.token, @id, name, colour, hoist, mentionable, permissions, reason)
3311
+
2485
3312
  role = Role.new(JSON.parse(response), @bot, self)
2486
3313
  @roles << role
2487
3314
  role
2488
3315
  end
2489
3316
 
2490
- # @return [Array<User>] a list of banned users on this server.
3317
+ # @return [Array<ServerBan>] a list of banned users on this server and the reason they were banned.
2491
3318
  def bans
2492
- users = JSON.parse(API::Server.bans(@bot.token, @id))
2493
- users.map { |e| User.new(e['user'], @bot) }
3319
+ response = JSON.parse(API::Server.bans(@bot.token, @id))
3320
+ response.map do |e|
3321
+ ServerBan.new(self, User.new(e['user'], @bot), e['reason'])
3322
+ end
2494
3323
  end
2495
3324
 
2496
3325
  # Bans a user from this server.
2497
3326
  # @param user [User, #resolve_id] The user to ban.
2498
3327
  # @param message_days [Integer] How many days worth of messages sent by the user should be deleted.
2499
- def ban(user, message_days = 0)
2500
- API::Server.ban_user(@bot.token, @id, user.resolve_id, message_days)
3328
+ # @param reason [String] The reason the user is being banned.
3329
+ def ban(user, message_days = 0, reason: nil)
3330
+ API::Server.ban_user(@bot.token, @id, user.resolve_id, message_days, reason)
2501
3331
  end
2502
3332
 
2503
3333
  # Unbans a previously banned user from this server.
2504
3334
  # @param user [User, #resolve_id] The user to unban.
2505
- def unban(user)
2506
- API::Server.unban_user(@bot.token, @id, user.resolve_id)
3335
+ # @param reason [String] The reason the user is being unbanned.
3336
+ def unban(user, reason = nil)
3337
+ API::Server.unban_user(@bot.token, @id, user.resolve_id, reason)
2507
3338
  end
2508
3339
 
2509
3340
  # Kicks a user from this server.
2510
3341
  # @param user [User, #resolve_id] The user to kick.
2511
- def kick(user)
2512
- API::Server.remove_member(@bot.token, @id, user.resolve_id)
3342
+ # @param reason [String] The reason the user is being kicked.
3343
+ def kick(user, reason = nil)
3344
+ API::Server.remove_member(@bot.token, @id, user.resolve_id, reason)
2513
3345
  end
2514
3346
 
2515
3347
  # Forcibly moves a user into a different voice channel. Only works if the bot has the permission needed.
@@ -2524,7 +3356,7 @@ module Discordrb
2524
3356
  API::Server.delete(@bot.token, @id)
2525
3357
  end
2526
3358
 
2527
- # Leave the server
3359
+ # Leave the server.
2528
3360
  def leave
2529
3361
  API::User.leave_server(@bot.token, @id)
2530
3362
  end
@@ -2541,6 +3373,22 @@ module Discordrb
2541
3373
  update_server_data(name: name)
2542
3374
  end
2543
3375
 
3376
+ # @return [Array<VoiceRegion>] collection of available voice regions to this guild
3377
+ def available_voice_regions
3378
+ return @available_voice_regions if @available_voice_regions
3379
+
3380
+ @available_voice_regions = {}
3381
+
3382
+ data = JSON.parse API::Server.regions(@bot.token, @id)
3383
+ @available_voice_regions = data.map { |e| VoiceRegion.new e }
3384
+ end
3385
+
3386
+ # @return [VoiceRegion, nil] voice region data for this server's region
3387
+ # @note This may return `nil` if this server's voice region is deprecated.
3388
+ def region
3389
+ available_voice_regions.find { |e| e.id == @region_id }
3390
+ end
3391
+
2544
3392
  # Moves the server to another region. This will cause a voice interruption of at most a second.
2545
3393
  # @param region [String] The new region the server should be in.
2546
3394
  def region=(region)
@@ -2553,9 +3401,9 @@ module Discordrb
2553
3401
  if icon.respond_to? :read
2554
3402
  icon_string = 'data:image/jpg;base64,'
2555
3403
  icon_string += Base64.strict_encode64(icon.read)
2556
- update_server_data(icon: icon_string)
3404
+ update_server_data(icon_id: icon_string)
2557
3405
  else
2558
- update_server_data(icon: icon)
3406
+ update_server_data(icon_id: icon)
2559
3407
  end
2560
3408
  end
2561
3409
 
@@ -2565,12 +3413,89 @@ module Discordrb
2565
3413
  update_server_data(afk_channel_id: afk_channel.resolve_id)
2566
3414
  end
2567
3415
 
3416
+ # Sets the server's system channel.
3417
+ # @param system_channel [Channel, String, Integer, #resolve_id, nil] The new system channel, or `nil` should it be disabled.
3418
+ def system_channel=(system_channel)
3419
+ update_server_data(system_channel_id: system_channel.resolve_id)
3420
+ end
3421
+
2568
3422
  # Sets the amount of time after which a user gets moved into the AFK channel.
2569
3423
  # @param afk_timeout [Integer] The AFK timeout, in seconds.
2570
3424
  def afk_timeout=(afk_timeout)
2571
3425
  update_server_data(afk_timeout: afk_timeout)
2572
3426
  end
2573
3427
 
3428
+ # A map of possible server verification levels to symbol names
3429
+ VERIFICATION_LEVELS = {
3430
+ none: 0,
3431
+ low: 1,
3432
+ medium: 2,
3433
+ high: 3,
3434
+ very_high: 4
3435
+ }.freeze
3436
+
3437
+ # @return [Symbol] the verification level of the server (:none = none, :low = 'Must have a verified email on their Discord account', :medium = 'Has to be registered with Discord for at least 5 minutes', :high = 'Has to be a member of this server for at least 10 minutes', :very_high = 'Must have a verified phone on their Discord account').
3438
+ def verification_level
3439
+ VERIFICATION_LEVELS.key @verification_level
3440
+ end
3441
+
3442
+ # Sets the verification level of the server
3443
+ # @param level [Integer, Symbol] The verification level from 0-4 or Symbol (see {VERIFICATION_LEVELS})
3444
+ def verification_level=(level)
3445
+ level = VERIFICATION_LEVELS[level] if level.is_a?(Symbol)
3446
+
3447
+ update_server_data(verification_level: level)
3448
+ end
3449
+
3450
+ # A map of possible message notification levels to symbol names
3451
+ NOTIFICATION_LEVELS = {
3452
+ all_messages: 0,
3453
+ only_mentions: 1
3454
+ }.freeze
3455
+
3456
+ # @return [Symbol] the default message notifications settings of the server (:all = 'All messages', :mentions = 'Only @mentions').
3457
+ def default_message_notifications
3458
+ NOTIFICATION_LEVELS.key @default_message_notifications
3459
+ end
3460
+
3461
+ # Sets the default message notification level
3462
+ # @param notification_level [Integer, Symbol] The default message notificiation 0-1 or Symbol (see {NOTIFICATION_LEVELS})
3463
+ def default_message_notifications=(notification_level)
3464
+ notification_level = NOTIFICATION_LEVELS[notification_level] if notification_level.is_a?(Symbol)
3465
+
3466
+ update_server_data(default_message_notifications: notification_level)
3467
+ end
3468
+
3469
+ alias_method :notification_level=, :default_message_notifications=
3470
+
3471
+ # Sets the server splash
3472
+ # @param splash_hash [String] The splash hash
3473
+ def splash=(splash_hash)
3474
+ update_server_data(splash: splash_hash)
3475
+ end
3476
+
3477
+ # A map of possible content filter levels to symbol names
3478
+ FILTER_LEVELS = {
3479
+ disabled: 0,
3480
+ members_without_roles: 1,
3481
+ all_members: 2
3482
+ }.freeze
3483
+
3484
+ # @return [Symbol] the explicit content filter level of the server (:none = 'Don't scan any messages.', :exclude_roles = 'Scan messages for members without a role.', :all = 'Scan messages sent by all members.').
3485
+ def explicit_content_filter
3486
+ FILTER_LEVELS.key @explicit_content_filter
3487
+ end
3488
+
3489
+ alias_method :content_filter_level, :explicit_content_filter
3490
+
3491
+ # Sets the server content filter.
3492
+ # @param filter_level [Integer, Symbol] The content filter from 0-2 or Symbol (see {FILTER_LEVELS})
3493
+ def explicit_content_filter=(filter_level)
3494
+ filter_level = FILTER_LEVELS[filter_level] if filter_level.is_a?(Symbol)
3495
+
3496
+ update_server_data(explicit_content_filter: filter_level)
3497
+ end
3498
+
2574
3499
  # @return [true, false] whether this server has any emoji or not.
2575
3500
  def any_emoji?
2576
3501
  @emoji.any?
@@ -2579,6 +3504,20 @@ module Discordrb
2579
3504
  alias_method :has_emoji?, :any_emoji?
2580
3505
  alias_method :emoji?, :any_emoji?
2581
3506
 
3507
+ # Requests a list of Webhooks on the server.
3508
+ # @return [Array<Webhook>] webhooks on the server.
3509
+ def webhooks
3510
+ webhooks = JSON.parse(API::Server.webhooks(@bot.token, @id))
3511
+ webhooks.map { |webhook| Webhook.new(webhook, @bot) }
3512
+ end
3513
+
3514
+ # Requests a list of Invites to the server.
3515
+ # @return [Array<Invite>] invites to the server.
3516
+ def invites
3517
+ invites = JSON.parse(API::Server.invites(@bot.token, @id))
3518
+ invites.map { |invite| Invite.new(invite, @bot) }
3519
+ end
3520
+
2582
3521
  # Processes a GUILD_MEMBERS_CHUNK packet, specifically the members field
2583
3522
  # @note For internal use only
2584
3523
  # @!visibility private
@@ -2597,23 +3536,39 @@ module Discordrb
2597
3536
  @processed_chunk_members = 0
2598
3537
  end
2599
3538
 
3539
+ # @return [Channel, nil] the AFK voice channel of this server, or `nil` if none is set.
3540
+ def afk_channel
3541
+ @bot.channel(@afk_channel_id) if @afk_channel_id
3542
+ end
3543
+
3544
+ # @return [Channel, nil] the system channel (used for automatic welcome messages) of a server, or `nil` if none is set.
3545
+ def system_channel
3546
+ @bot.channel(@system_channel_id) if @system_channel_id
3547
+ end
3548
+
2600
3549
  # Updates the cached data with new data
2601
3550
  # @note For internal use only
2602
3551
  # @!visibility private
2603
- def update_data(new_data)
3552
+ def update_data(new_data = nil)
3553
+ new_data ||= JSON.parse(API::Server.resolve(@bot.token, @id))
2604
3554
  @name = new_data[:name] || new_data['name'] || @name
2605
- @region = new_data[:region] || new_data['region'] || @region
3555
+ @region_id = new_data[:region] || new_data['region'] || @region_id
2606
3556
  @icon_id = new_data[:icon] || new_data['icon'] || @icon_id
2607
- @afk_timeout = new_data[:afk_timeout] || new_data['afk_timeout'].to_i || @afk_timeout
3557
+ @afk_timeout = new_data[:afk_timeout] || new_data['afk_timeout'] || @afk_timeout
2608
3558
 
2609
- @afk_channel_id = new_data[:afk_channel_id] || new_data['afk_channel_id'].to_i || @afk_channel.id
3559
+ afk_channel_id = new_data[:afk_channel_id] || new_data['afk_channel_id'] || @afk_channel
3560
+ @afk_channel_id = afk_channel_id.nil? ? nil : afk_channel_id.resolve_id
3561
+ embed_channel_id = new_data[:embed_channel_id] || new_data['embed_channel_id'] || @embed_channel
3562
+ @embed_channel_id = embed_channel_id.nil? ? nil : embed_channel_id.resolve_id
3563
+ system_channel_id = new_data[:system_channel_id] || new_data['system_channel_id'] || @system_channel
3564
+ @system_channel_id = system_channel_id.nil? ? nil : system_channel_id.resolve_id
2610
3565
 
2611
- begin
2612
- @afk_channel = @bot.channel(@afk_channel_id, self) if @afk_channel_id.nonzero? && (!@afk_channel || @afk_channel_id != @afk_channel.id)
2613
- rescue Discordrb::Errors::NoPermission
2614
- LOGGER.debug("AFK channel #{@afk_channel_id} on server #{@id} is unreachable, setting to nil even though one exists")
2615
- @afk_channel = nil
2616
- end
3566
+ @embed_enabled = new_data[:embed_enabled] || new_data['embed_enabled']
3567
+ @splash = new_data[:splash_id] || new_data['splash_id'] || @splash_id
3568
+
3569
+ @verification_level = new_data[:verification_level] || new_data['verification_level'] || @verification_level
3570
+ @explicit_content_filter = new_data[:explicit_content_filter] || new_data['explicit_content_filter'] || @explicit_content_filter
3571
+ @default_message_notifications = new_data[:default_message_notifications] || new_data['default_message_notifications'] || @default_message_notifications
2617
3572
  end
2618
3573
 
2619
3574
  # Adds a channel to this server's cache
@@ -2642,19 +3597,24 @@ module Discordrb
2642
3597
 
2643
3598
  # The inspect method is overwritten to give more useful output
2644
3599
  def inspect
2645
- "<Server name=#{@name} id=#{@id} large=#{@large} region=#{@region} owner=#{@owner} afk_channel_id=#{@afk_channel_id} afk_timeout=#{@afk_timeout}>"
3600
+ "<Server name=#{@name} id=#{@id} large=#{@large} region=#{@region} owner=#{@owner} afk_channel_id=#{@afk_channel_id} system_channel_id=#{@system_channel_id} afk_timeout=#{@afk_timeout}>"
2646
3601
  end
2647
3602
 
2648
3603
  private
2649
3604
 
2650
3605
  def update_server_data(new_data)
2651
- API::Server.update(@bot.token, @id,
2652
- new_data[:name] || @name,
2653
- new_data[:region] || @region,
2654
- new_data[:icon_id] || @icon_id,
2655
- new_data[:afk_channel_id] || @afk_channel_id,
2656
- new_data[:afk_timeout] || @afk_timeout)
2657
- update_data(new_data)
3606
+ response = JSON.parse(API::Server.update(@bot.token, @id,
3607
+ new_data[:name] || @name,
3608
+ new_data[:region] || @region_id,
3609
+ new_data[:icon_id] || @icon_id,
3610
+ new_data[:afk_channel_id] || @afk_channel_id,
3611
+ new_data[:afk_timeout] || @afk_timeout,
3612
+ new_data[:splash] || @splash,
3613
+ new_data[:default_message_notifications] || @default_message_notifications,
3614
+ new_data[:verification_level] || @verification_level,
3615
+ new_data[:explicit_content_filter] || @explicit_content_filter,
3616
+ new_data[:system_channel_id] || @system_channel_id))
3617
+ update_data(response)
2658
3618
  end
2659
3619
 
2660
3620
  def process_roles(roles)
@@ -2721,6 +3681,477 @@ module Discordrb
2721
3681
  end
2722
3682
  end
2723
3683
 
3684
+ # A ban entry on a server
3685
+ class ServerBan
3686
+ # @return [String, nil] the reason the user was banned, if provided
3687
+ attr_reader :reason
3688
+
3689
+ # @return [User] the user that was banned
3690
+ attr_reader :user
3691
+
3692
+ # @return [Server] the server this ban belongs to
3693
+ attr_reader :server
3694
+
3695
+ # @!visibility private
3696
+ def initialize(server, user, reason)
3697
+ @server = server
3698
+ @user = user
3699
+ @reason = reason
3700
+ end
3701
+
3702
+ # Removes this ban on the associated user in the server
3703
+ # @param reason [String] the reason for removing the ban
3704
+ def remove(reason = nil)
3705
+ @server.unban(user, reason)
3706
+ end
3707
+
3708
+ alias_method :unban, :remove
3709
+ alias_method :lift, :remove
3710
+ end
3711
+
3712
+ # A webhook on a server channel
3713
+ class Webhook
3714
+ include IDObject
3715
+
3716
+ # @return [String] the webhook name.
3717
+ attr_reader :name
3718
+
3719
+ # @return [Channel] the channel that the webhook is currently connected to.
3720
+ attr_reader :channel
3721
+
3722
+ # @return [Server] the server that the webhook is currently connected to.
3723
+ attr_reader :server
3724
+
3725
+ # @return [String] the webhook's token.
3726
+ attr_reader :token
3727
+
3728
+ # @return [String] the webhook's avatar id.
3729
+ attr_reader :avatar
3730
+
3731
+ # Gets the user object of the creator of the webhook. May be limited to username, discriminator,
3732
+ # ID and avatar if the bot cannot reach the owner
3733
+ # @return [Member, User, nil] the user object of the owner or nil if the webhook was requested using the token.
3734
+ attr_reader :owner
3735
+
3736
+ def initialize(data, bot)
3737
+ @bot = bot
3738
+
3739
+ @name = data['name']
3740
+ @id = data['id'].to_i
3741
+ @channel = bot.channel(data['channel_id'])
3742
+ @server = @channel.server
3743
+ @token = data['token']
3744
+ @avatar = data['avatar']
3745
+
3746
+ # Will not exist if the data was requested through a webhook token
3747
+ return unless data['user']
3748
+ @owner = @server.member(data['user']['id'].to_i)
3749
+ return if @owner
3750
+ Discordrb::LOGGER.debug("Member with ID #{data['user']['id']} not cached (possibly left the server).")
3751
+ @owner = @bot.ensure_user(data['user'])
3752
+ end
3753
+
3754
+ # Sets the webhook's avatar.
3755
+ # @param avatar [String, #read] The new avatar, in base64-encoded JPG format.
3756
+ def avatar=(avatar)
3757
+ update_webhook(avatar: avatarise(avatar))
3758
+ end
3759
+
3760
+ # Deletes the webhook's avatar.
3761
+ def delete_avatar
3762
+ update_webhook(avatar: nil)
3763
+ end
3764
+
3765
+ # Sets the webhook's channel
3766
+ # @param channel [Channel, String, Integer, #resolve_id] The channel the webhook should use.
3767
+ def channel=(channel)
3768
+ update_webhook(channel_id: channel.resolve_id)
3769
+ end
3770
+
3771
+ # Sets the webhook's name.
3772
+ # @param name [String] The webhook's new name.
3773
+ def name=(name)
3774
+ update_webhook(name: name)
3775
+ end
3776
+
3777
+ # Updates the webhook if you need to edit more than 1 attribute.
3778
+ # @param data [Hash] the data to update.
3779
+ # @option data [String, #read, nil] :avatar The new avatar, in base64-encoded JPG format, or nil to delete the avatar.
3780
+ # @option data [Channel, String, Integer, #resolve_id] :channel The channel the webhook should use.
3781
+ # @option data [String] :name The webhook's new name.
3782
+ # @option data [String] :reason The reason for the webhook changes.
3783
+ def update(data)
3784
+ # Only pass a value for avatar if the key is defined as sending nil will delete the
3785
+ data[:avatar] = avatarise(data[:avatar]) if data.key?(:avatar)
3786
+ data[:channel_id] = data[:channel].resolve_id
3787
+ data.delete(:channel)
3788
+ update_webhook(data)
3789
+ end
3790
+
3791
+ # Deletes the webhook.
3792
+ # @param reason [String] The reason the invite is being deleted.
3793
+ def delete(reason = nil)
3794
+ if token?
3795
+ API::Webhook.token_delete_webhook(@token, @id, reason)
3796
+ else
3797
+ API::Webhook.delete_webhook(@bot.token, @id, reason)
3798
+ end
3799
+ end
3800
+
3801
+ # Utility function to get a webhook's avatar URL.
3802
+ # @return [String] the URL to the avatar image
3803
+ def avatar_url
3804
+ return API::User.default_avatar unless @avatar
3805
+ API::User.avatar_url(@id, @avatar)
3806
+ end
3807
+
3808
+ # The `inspect` method is overwritten to give more useful output.
3809
+ def inspect
3810
+ "<Webhook name=#{@name} id=#{@id}>"
3811
+ end
3812
+
3813
+ # Utility function to know if the webhook was requested through a webhook token, rather than auth.
3814
+ # @return [true, false] whether the webhook was requested by token or not.
3815
+ def token?
3816
+ @owner.nil?
3817
+ end
3818
+
3819
+ private
3820
+
3821
+ def avatarise(avatar)
3822
+ if avatar.respond_to? :read
3823
+ "data:image/jpg;base64,#{Base64.strict_encode64(avatar.read)}"
3824
+ else
3825
+ avatar
3826
+ end
3827
+ end
3828
+
3829
+ def update_internal(data)
3830
+ @name = data['name']
3831
+ @avatar_id = data['avatar']
3832
+ @channel = @bot.channel(data['channel_id'])
3833
+ end
3834
+
3835
+ def update_webhook(new_data)
3836
+ reason = new_data.delete(:reason)
3837
+ data = JSON.parse(if token?
3838
+ API::Webhook.token_update_webhook(@token, @id, new_data, reason)
3839
+ else
3840
+ API::Webhook.update_webhook(@bot.token, @id, new_data, reason)
3841
+ end)
3842
+ # Only update cache if API call worked
3843
+ update_internal(data) if data['name']
3844
+ end
3845
+ end
3846
+
3847
+ # A server's audit logs
3848
+ class AuditLogs
3849
+ # The numbers associated with the type of action.
3850
+ Actions = {
3851
+ 1 => :server_update,
3852
+ 10 => :channel_create,
3853
+ 11 => :channel_update,
3854
+ 12 => :channel_delete,
3855
+ 13 => :channel_overwrite_create,
3856
+ 14 => :channel_overwrite_update,
3857
+ 15 => :channel_overwrite_delete,
3858
+ 20 => :member_kick,
3859
+ 21 => :member_prune,
3860
+ 22 => :member_ban_add,
3861
+ 23 => :member_ban_remove,
3862
+ 24 => :member_update,
3863
+ 25 => :member_role_update,
3864
+ 30 => :role_create,
3865
+ 31 => :role_update,
3866
+ 32 => :role_delete,
3867
+ 40 => :invite_create,
3868
+ 41 => :invite_update,
3869
+ 42 => :invite_delete,
3870
+ 50 => :webhook_create,
3871
+ 51 => :webhook_update,
3872
+ 52 => :webhook_delete,
3873
+ 60 => :emoji_create,
3874
+ 61 => :emoji_update,
3875
+ 62 => :emoji_delete,
3876
+ # 70
3877
+ # 71
3878
+ 72 => :message_delete
3879
+ }.freeze
3880
+
3881
+ # @return [Hash<String => User>] the users included in the audit logs.
3882
+ attr_reader :users
3883
+
3884
+ # @return [Hash<String => Webhook>] the webhooks included in the audit logs.
3885
+ attr_reader :webhooks
3886
+
3887
+ # @return [Array<Entry>] the entries listed in the audit logs.
3888
+ attr_reader :entries
3889
+
3890
+ # @!visibility private
3891
+ def initialize(server, bot, data)
3892
+ @bot = bot
3893
+ @server = server
3894
+ @users = {}
3895
+ @webhooks = {}
3896
+ @entries = data['audit_log_entries'].map { |entry| Entry.new(self, @server, @bot, entry) }
3897
+
3898
+ process_users(data['users'])
3899
+ process_webhooks(data['webhooks'])
3900
+ end
3901
+
3902
+ # An entry in a server's audit logs.
3903
+ class Entry
3904
+ include IDObject
3905
+
3906
+ # @return [Symbol] the action that was performed.
3907
+ attr_reader :action
3908
+
3909
+ # @return [Symbol] the type action that was performed. (:create, :delete, :update, :unknown)
3910
+ attr_reader :action_type
3911
+
3912
+ # @return [Symbol] the type of target being performed on. (:server, :channel, :user, :role, :invite, :webhook, :emoji, :unknown)
3913
+ attr_reader :target_type
3914
+
3915
+ # @return [Integer, nil] the amount of messages deleted. Only present if the action is `:message_delete`.
3916
+ attr_reader :count
3917
+ alias_method :amount, :count
3918
+
3919
+ # @return [Integer, nil] the amount of days the members were inactive for. Only present if the action is `:member_prune`.
3920
+ attr_reader :days
3921
+
3922
+ # @return [Integer, nil] the amount of members removed. Only present if the action is `:member_prune`.
3923
+ attr_reader :members_removed
3924
+
3925
+ # @return [String, nil] the reason for this action occuring.
3926
+ attr_reader :reason
3927
+
3928
+ # @return [Hash<String => Change>, RoleChange, nil] the changes from this log, listing the key as the key changed. Will be a RoleChange object if the action is `:member_role_update`. Will be nil if the action is either `:message_delete` or `:member_prune`.
3929
+ attr_reader :changes
3930
+
3931
+ # @!visibility private
3932
+ def initialize(logs, server, bot, data)
3933
+ @bot = bot
3934
+ @id = data['id'].resolve_id
3935
+ @logs = logs
3936
+ @server = server
3937
+ @data = data
3938
+ @action = Actions[data['action_type']]
3939
+ @reason = data['reason']
3940
+ @action_type = AuditLogs.action_type_for(data['action_type'])
3941
+ @target_type = AuditLogs.target_type_for(data['action_type'])
3942
+
3943
+ # Sets the 'changes' variable to a empty hash if there are no special actions.
3944
+ @changes = {} unless @action == :message_delete || @action == :member_prune || @action == :member_role_update
3945
+
3946
+ # Sets the 'changes' variable to a RoleChange class if theres a role update.
3947
+ @changes = RoleChange.new(data['changes'][0], @server) if @action == :member_role_update
3948
+
3949
+ process_changes(data['changes']) unless @action == :member_role_update
3950
+ return unless data.include?('options')
3951
+
3952
+ # Checks and sets variables for special action options.
3953
+ @count = data['options']['count'].to_i unless data['options']['count'].nil?
3954
+ @channel_id = data['options']['channel'].to_i unless data['options']['channel'].nil?
3955
+ @days = data['options']['delete_member_days'].to_i unless data['options']['delete_member_days'].nil?
3956
+ @members_removed = data['options']['members_removed'].to_i unless data['options']['members_removed'].nil?
3957
+ end
3958
+
3959
+ # @return [Server, Channel, Member, User, Role, Invite, Webhook, Emoji, nil] the target being performed on.
3960
+ def target
3961
+ @target ||= process_target(@data['target_id'], @target_type)
3962
+ end
3963
+
3964
+ # @return [Member, User] the user that authored this action. Can be a User object if the user no longer exists in the server.
3965
+ def user
3966
+ @user ||= @server.member(@data['user_id'].to_i) || @bot.user(@data['user_id'].to_i) || @logs.user(@data['user_id'].to_i)
3967
+ end
3968
+ alias_method :author, :user
3969
+
3970
+ # @return [Channel, nil] the amount of messages deleted. Won't be nil if the action is `:message_delete`.
3971
+ def channel
3972
+ return nil unless @channel_id
3973
+ @channel ||= @bot.channel(@channel_id, @server, bot, self)
3974
+ end
3975
+
3976
+ # @!visibility private
3977
+ def process_target(id, type)
3978
+ id = id.resolve_id unless id.nil?
3979
+ case type
3980
+ when :server then @server # Since it won't be anything else
3981
+ when :channel then @bot.channel(id, @server)
3982
+ when :user, :message then @server.member(id) || @bot.user(id) || @logs.user(id)
3983
+ when :role then @server.role(id)
3984
+ when :invite then @bot.invite(@data['changes'].find { |change| change['key'] == 'code' }.values.delete_if { |v| v == 'code' }.first)
3985
+ when :webhook then @server.webhooks.find { |webhook| webhook.id == id } || @logs.webhook(id)
3986
+ when :emoji then @server.emoji[id]
3987
+ end
3988
+ end
3989
+
3990
+ # The inspect method is overwritten to give more useful output
3991
+ def inspect
3992
+ "<AuditLogs::Entry id=#{@id} action=#{@action} reason=#{@reason} action_type=#{@action_type} target_type=#{@target_type} count=#{@count} days=#{@days} members_removed=#{@members_removed}>"
3993
+ end
3994
+
3995
+ # Process action changes
3996
+ # @note For internal use only
3997
+ # @!visibility private
3998
+ def process_changes(changes)
3999
+ return unless changes
4000
+ changes.each do |element|
4001
+ change = Change.new(element, @server, @bot, self)
4002
+ @changes[change.key] = change
4003
+ end
4004
+ end
4005
+ end
4006
+
4007
+ # A change in a audit log entry.
4008
+ class Change
4009
+ # @return [String] the key that was changed.
4010
+ # @note You should check with the Discord API Documentation on what key gives out what value.
4011
+ attr_reader :key
4012
+
4013
+ # @return [String, Integer, true, false, Permissions, Overwrite, nil] the value that was changed from.
4014
+ attr_reader :old
4015
+ alias_method :old_value, :old
4016
+
4017
+ # @return [String, Integer, true, false, Permissions, Overwrite, nil] the value that was changed to.
4018
+ attr_reader :new
4019
+ alias_method :new_value, :new
4020
+
4021
+ # @!visibility private
4022
+ def initialize(data, server, bot, logs)
4023
+ @key = data['key']
4024
+ @old = data['old_value']
4025
+ @new = data['new_value']
4026
+ @server = server
4027
+ @bot = bot
4028
+ @logs = logs
4029
+
4030
+ @old = Permissions.new(@old) if @old && @key == 'permissions'
4031
+ @new = Permissions.new(@new) if @new && @key == 'permissions'
4032
+
4033
+ @old = @old.map { |o| Overwrite.new(o['id'], type: o['type'].to_sym, allow: o['allow'], deny: o['deny']) } if @old && @key == 'permission_overwrites'
4034
+ @new = @new.map { |o| Overwrite.new(o['id'], type: o['type'].to_sym, allow: o['allow'], deny: o['deny']) } if @new && @key == 'permission_overwrites'
4035
+ end
4036
+
4037
+ # @return [Channel, nil] the channel that was previously used in the server widget. Only present if the key for this change is `widget_channel_id`.
4038
+ def old_widget_channel
4039
+ @bot.channel(@old, @server) if @old && @key == 'widget_channel_id'
4040
+ end
4041
+
4042
+ # @return [Channel, nil] the channel that is used in the server widget prior to this change. Only present if the key for this change is `widget_channel_id`.
4043
+ def new_widget_channel
4044
+ @bot.channel(@new, @server) if @new && @key == 'widget_channel_id'
4045
+ end
4046
+
4047
+ # @return [Channel, nil] the channel that was previously used in the server as an AFK channel. Only present if the key for this change is `afk_channel_id`.
4048
+ def old_afk_channel
4049
+ @bot.channel(@old, @server) if @old && @key == 'afk_channel_id'
4050
+ end
4051
+
4052
+ # @return [Channel, nil] the channel that is used in the server as an AFK channel prior to this change. Only present if the key for this change is `afk_channel_id`.
4053
+ def new_afk_channel
4054
+ @bot.channel(@new, @server) if @new && @key == 'afk_channel_id'
4055
+ end
4056
+
4057
+ # @return [Member, User, nil] the member that used to be the owner of the server. Only present if the for key for this change is `owner_id`.
4058
+ def old_owner
4059
+ @server.member(@old) || @bot.user(@old) || @logs.user(@old) if @old && @key == 'owner_id'
4060
+ end
4061
+
4062
+ # @return [Member, User, nil] the member that is now the owner of the server prior to this change. Only present if the key for this change is `owner_id`.
4063
+ def new_owner
4064
+ @server.member(@new) || @bot.user(@new) || @logs.user(@new) if @new && @key == 'owner_id'
4065
+ end
4066
+ end
4067
+
4068
+ # A change that includes roles.
4069
+ class RoleChange
4070
+ # @return [Symbol] what type of change this is: (:add, :remove)
4071
+ attr_reader :type
4072
+
4073
+ # @!visibility private
4074
+ def initialize(data, server)
4075
+ @type = data['key'].delete('$').to_sym
4076
+ @role_id = data['new_value'][0]['id'].to_i
4077
+ @server = server
4078
+ end
4079
+
4080
+ # @return [Role] the role being used.
4081
+ def role
4082
+ @role ||= @server.role(@role_id)
4083
+ end
4084
+ end
4085
+
4086
+ # @return [Entry] the latest entry in the audit logs.
4087
+ def latest
4088
+ @entries.first
4089
+ end
4090
+ alias_method :first, :latest
4091
+
4092
+ # Gets a user in the audit logs data based on user ID
4093
+ # @note This only uses data given by the audit logs request
4094
+ # @param id [#resolve_id] The user ID to look for
4095
+ def user(id)
4096
+ @users[id.resolve_id]
4097
+ end
4098
+
4099
+ # Gets a webhook in the audit logs data based on webhook ID
4100
+ # @note This only uses data given by the audit logs request
4101
+ # @param id [#resolve_id] The webhook ID to look for
4102
+ def webhook(id)
4103
+ @webhooks[id.resolve_id]
4104
+ end
4105
+
4106
+ # Process user objects given by the request
4107
+ # @note For internal use only
4108
+ # @!visibility private
4109
+ def process_users(users)
4110
+ users.each do |element|
4111
+ user = User.new(element, @bot)
4112
+ @users[user.id] = user
4113
+ end
4114
+ end
4115
+
4116
+ # Process webhook objects given by the request
4117
+ # @note For internal use only
4118
+ # @!visibility private
4119
+ def process_webhooks(webhooks)
4120
+ webhooks.each do |element|
4121
+ webhook = Webhook.new(element, @bot)
4122
+ @webhooks[webhook.id] = webhook
4123
+ end
4124
+ end
4125
+
4126
+ # Find the type of target by it's action number
4127
+ # @note For internal use only
4128
+ # @!visibility private
4129
+ def self.target_type_for(action)
4130
+ case action
4131
+ when 1..9 then :server
4132
+ when 10..19 then :channel
4133
+ when 20..29 then :user
4134
+ when 30..39 then :role
4135
+ when 40..49 then :invite
4136
+ when 50..59 then :webhook
4137
+ when 60..69 then :emoji
4138
+ when 70..79 then :message
4139
+ else :unknown
4140
+ end
4141
+ end
4142
+
4143
+ # Find the type of action by its action number
4144
+ # @note For internal use only
4145
+ # @!visibility private
4146
+ def self.action_type_for(action)
4147
+ action = Actions[action]
4148
+ return :create if %i[channel_create channel_overwrite_create member_ban_add role_create invite_create webhook_create emoji_create].include?(action)
4149
+ return :delete if %i[channel_delete channel_overwrite_delete member_kick member_prune member_ban_remove role_delete invite_delete webhook_delete emoji_delete message_delete].include?(action)
4150
+ return :update if %i[server_update channel_update channel_overwrite_update member_update member_role_update role_update invite_update webhook_update emoji_update].include?(action)
4151
+ :unknown
4152
+ end
4153
+ end
4154
+
2724
4155
  # A colour (red, green and blue values). Used for role colours. If you prefer the American spelling, the alias
2725
4156
  # {ColorRGB} is also available.
2726
4157
  class ColourRGB
@@ -2735,15 +4166,27 @@ module Discordrb
2735
4166
 
2736
4167
  # @return [Integer] the colour's RGB values combined into one integer.
2737
4168
  attr_reader :combined
4169
+ alias_method :to_i, :combined
2738
4170
 
2739
4171
  # Make a new colour from the combined value.
2740
- # @param combined [Integer] The colour's RGB values combined into one integer
4172
+ # @param combined [Integer, String] The colour's RGB values combined into one integer or a hexadecimal string
4173
+ # @example Initialize a with a base 10 integer
4174
+ # ColourRGB.new(7506394) #=> ColourRGB
4175
+ # ColourRGB.new(0x7289da) #=> ColourRGB
4176
+ # @example Initialize a with a hexadecimal string
4177
+ # ColourRGB.new('7289da') #=> ColourRGB
2741
4178
  def initialize(combined)
2742
- @combined = combined
2743
- @red = (combined >> 16) & 0xFF
2744
- @green = (combined >> 8) & 0xFF
2745
- @blue = combined & 0xFF
4179
+ @combined = combined.is_a?(String) ? combined.to_i(16) : combined
4180
+ @red = (@combined >> 16) & 0xFF
4181
+ @green = (@combined >> 8) & 0xFF
4182
+ @blue = @combined & 0xFF
4183
+ end
4184
+
4185
+ # @return [String] the colour as a hexadecimal.
4186
+ def hex
4187
+ @combined.to_s(16)
2746
4188
  end
4189
+ alias_method :hexadecimal, :hex
2747
4190
  end
2748
4191
 
2749
4192
  # Alias for the class {ColourRGB}