discordrb 2.1.3 → 3.0.0

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

Potentially problematic release.


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

@@ -4,7 +4,12 @@
4
4
 
5
5
  require 'ostruct'
6
6
  require 'discordrb/permissions'
7
+ require 'discordrb/errors'
7
8
  require 'discordrb/api'
9
+ require 'discordrb/api/channel'
10
+ require 'discordrb/api/server'
11
+ require 'discordrb/api/invite'
12
+ require 'discordrb/api/user'
8
13
  require 'discordrb/events/message'
9
14
  require 'time'
10
15
  require 'base64'
@@ -49,7 +54,7 @@ module Discordrb
49
54
 
50
55
  # If it's still larger than the character limit (none was smaller than it) split it into slices with the length
51
56
  # being the character limit, otherwise just return an array with one element
52
- ideal_ary = (ideal.length > CHARACTER_LIMIT) ? ideal.chars.each_slice(CHARACTER_LIMIT).map(&:join) : [ideal]
57
+ ideal_ary = ideal.length > CHARACTER_LIMIT ? ideal.chars.each_slice(CHARACTER_LIMIT).map(&:join) : [ideal]
53
58
 
54
59
  # Slice off the ideal part and strip newlines
55
60
  rest = msg[ideal.length..-1].strip
@@ -117,7 +122,7 @@ module Discordrb
117
122
  # Utility function to get a user's avatar URL.
118
123
  # @return [String] the URL to the avatar image.
119
124
  def avatar_url
120
- API.avatar_url(@id, @avatar_id)
125
+ API::User.avatar_url(@id, @avatar_id)
121
126
  end
122
127
  end
123
128
 
@@ -164,10 +169,20 @@ module Discordrb
164
169
  channel.send_message(content)
165
170
  else
166
171
  # If no message was specified, return the PM channel
167
- @bot.private_channel(@id)
172
+ @bot.pm_channel(@id)
168
173
  end
169
174
  end
170
175
 
176
+ alias_method :dm, :pm
177
+
178
+ # Send the user a file.
179
+ # @param file [File] The file to send to the user
180
+ # @param caption [String] The caption of the file being sent
181
+ # @return [Message] the message sent to this user.
182
+ def send_file(file, caption = nil)
183
+ pm.send_file(file, caption: caption)
184
+ end
185
+
171
186
  # Set the user's name
172
187
  # @note for internal use only
173
188
  # @!visibility private
@@ -208,24 +223,54 @@ module Discordrb
208
223
  end
209
224
  end
210
225
 
211
- # Mixin for the attributes members and private members should have
212
- module MemberAttributes
213
- # @return [true, false] whether this member is muted server-wide.
214
- attr_reader :mute
215
- alias_method :muted?, :mute
226
+ # OAuth Application information
227
+ class Application
228
+ include IDObject
216
229
 
217
- # @return [true, false] whether this member is deafened server-wide.
218
- attr_reader :deaf
219
- alias_method :deafened?, :deaf
230
+ # @return [String] the application name
231
+ attr_reader :name
220
232
 
221
- # @return [true, false] whether this member has muted themselves.
222
- attr_reader :self_mute
223
- alias_method :self_muted?, :self_mute
233
+ # @return [String] the application description
234
+ attr_reader :description
224
235
 
225
- # @return [true, false] whether this member has deafened themselves.
226
- attr_reader :self_deaf
227
- alias_method :self_deafened?, :self_deaf
236
+ # @return [Array<String>] the applications origins permitted to use RPC
237
+ attr_reader :rpc_origins
238
+
239
+ # @return [Integer]
240
+ attr_reader :flags
241
+
242
+ # Gets the user object of the owner. May be limited to username, discriminator,
243
+ # ID and avatar if the bot cannot reach the owner.
244
+ # @return [User] the user object of the owner
245
+ attr_reader :owner
246
+
247
+ def initialize(data, bot)
248
+ @bot = bot
249
+
250
+ @name = data['name']
251
+ @id = data['id'].to_i
252
+ @description = data['description']
253
+ @icon_id = data['icon']
254
+ @rpc_origins = data['rpc_origins']
255
+ @flags = data['flags']
256
+ @owner = @bot.ensure_user(data['owner'])
257
+ end
228
258
 
259
+ # Utility function to get a application's icon URL.
260
+ # @return [String, nil] the URL to the icon image (nil if no iamge is set).
261
+ def icon_url
262
+ return nil if @icon_id.nil?
263
+ API.app_icon_url(@id, @icon_id)
264
+ end
265
+
266
+ # The inspect method is overwritten to give more useful output
267
+ def inspect
268
+ "<Application name=#{@name} id=#{@id}>"
269
+ end
270
+ end
271
+
272
+ # Mixin for the attributes members and private members should have
273
+ module MemberAttributes
229
274
  # @return [Time] when this member joined the server.
230
275
  attr_reader :joined_at
231
276
 
@@ -238,9 +283,6 @@ module Discordrb
238
283
 
239
284
  # @return [Server] the server this member is on.
240
285
  attr_reader :server
241
-
242
- # @return [Channel] the voice channel the user is in.
243
- attr_reader :voice_channel
244
286
  end
245
287
 
246
288
  # Mixin to calculate resulting permissions from overrides etc.
@@ -258,7 +300,7 @@ module Discordrb
258
300
  # (Coincidentally, Manage Permissions is the same permission as Manage Roles, and a
259
301
  # Manage Permissions deny overwrite will override Manage Roles, so we can just check for
260
302
  # Manage Roles once and call it a day.)
261
- return true if defined_permission?(:manage_roles, channel)
303
+ return true if defined_permission?(:administrator, channel)
262
304
 
263
305
  # Otherwise, defer to defined_permission
264
306
  defined_permission?(action, channel)
@@ -289,6 +331,8 @@ module Discordrb
289
331
  end
290
332
  end
291
333
 
334
+ alias_method :can_administrate?, :can_administrator?
335
+
292
336
  private
293
337
 
294
338
  def defined_role_permission?(action, channel)
@@ -327,9 +371,77 @@ module Discordrb
327
371
  end
328
372
  end
329
373
 
374
+ # A voice state represents the state of a member's connection to a voice channel. It includes data like the voice
375
+ # channel the member is connected to and mute/deaf flags.
376
+ class VoiceState
377
+ # @return [Integer] the ID of the user whose voice state is represented by this object.
378
+ attr_reader :user_id
379
+
380
+ # @return [true, false] whether this voice state's member is muted server-wide.
381
+ attr_reader :mute
382
+
383
+ # @return [true, false] whether this voice state's member is deafened server-wide.
384
+ attr_reader :deaf
385
+
386
+ # @return [true, false] whether this voice state's member has muted themselves.
387
+ attr_reader :self_mute
388
+
389
+ # @return [true, false] whether this voice state's member has deafened themselves.
390
+ attr_reader :self_deaf
391
+
392
+ # @return [Channel] the voice channel this voice state's member is in.
393
+ attr_reader :voice_channel
394
+
395
+ # @!visibility private
396
+ def initialize(user_id)
397
+ @user_id = user_id
398
+ end
399
+
400
+ # Update this voice state with new data from Discord
401
+ # @note For internal use only.
402
+ # @!visibility private
403
+ def update(channel, mute, deaf, self_mute, self_deaf)
404
+ @voice_channel = channel
405
+ @mute = mute
406
+ @deaf = deaf
407
+ @self_mute = self_mute
408
+ @self_deaf = self_deaf
409
+ end
410
+ end
411
+
330
412
  # A member is a user on a server. It differs from regular users in that it has roles, voice statuses and things like
331
413
  # that.
332
414
  class Member < DelegateClass(User)
415
+ # @return [true, false] whether this member is muted server-wide.
416
+ def mute
417
+ voice_state_attribute(:mute)
418
+ end
419
+
420
+ # @return [true, false] whether this member is deafened server-wide.
421
+ def deaf
422
+ voice_state_attribute(:deaf)
423
+ end
424
+
425
+ # @return [true, false] whether this member has muted themselves.
426
+ def self_mute
427
+ voice_state_attribute(:self_mute)
428
+ end
429
+
430
+ # @return [true, false] whether this member has deafened themselves.
431
+ def self_deaf
432
+ voice_state_attribute(:self_deaf)
433
+ end
434
+
435
+ # @return [Channel] the voice channel this member is in.
436
+ def voice_channel
437
+ voice_state_attribute(:voice_channel)
438
+ end
439
+
440
+ alias_method :muted?, :mute
441
+ alias_method :deafened?, :deaf
442
+ alias_method :self_muted?, :self_mute
443
+ alias_method :self_deafened?, :self_deaf
444
+
333
445
  include MemberAttributes
334
446
 
335
447
  # @!visibility private
@@ -347,9 +459,6 @@ module Discordrb
347
459
  update_roles(data['roles'])
348
460
 
349
461
  @nick = data['nick']
350
-
351
- @deaf = data['deaf']
352
- @mute = data['mute']
353
462
  @joined_at = data['joined_at'] ? Time.parse(data['joined_at']) : nil
354
463
  end
355
464
 
@@ -365,6 +474,25 @@ module Discordrb
365
474
  @roles.any? { |e| e.id == role }
366
475
  end
367
476
 
477
+ # Bulk sets a member's roles.
478
+ # @param role [Role, Array<Role>] The role(s) to set.
479
+ def roles=(role)
480
+ role_ids = role_id_array(role)
481
+ API::Server.update_member(@bot.token, @server.id, @user.id, roles: role_ids)
482
+ end
483
+
484
+ # Adds and removes roles from a member.
485
+ # @param add [Role, Array<Role>] The role(s) to add.
486
+ # @param remove [Role, Array<Role>] The role(s) to remove.
487
+ def modify_roles(add, remove)
488
+ add_role_ids = role_id_array(add)
489
+ remove_role_ids = role_id_array(remove)
490
+ old_role_ids = @roles.map(&:id)
491
+ new_role_ids = (old_role_ids - remove_role_ids + add_role_ids).uniq
492
+
493
+ API::Server.update_member(@bot.token, @server.id, @user.id, roles: new_role_ids)
494
+ end
495
+
368
496
  # Adds one or more roles to this member.
369
497
  # @param role [Role, Array<Role>] The role(s) to add.
370
498
  def add_role(role)
@@ -372,7 +500,7 @@ module Discordrb
372
500
  old_role_ids = @roles.map(&:id)
373
501
  new_role_ids = (old_role_ids + role_ids).uniq
374
502
 
375
- API.update_user_roles(@bot.token, @server.id, @user.id, new_role_ids)
503
+ API::Server.update_member(@bot.token, @server.id, @user.id, roles: new_role_ids)
376
504
  end
377
505
 
378
506
  # Removes one or more roles from this member.
@@ -382,7 +510,27 @@ module Discordrb
382
510
  role_ids = role_id_array(role)
383
511
  new_role_ids = old_role_ids.reject { |i| role_ids.include?(i) }
384
512
 
385
- API.update_user_roles(@bot.token, @server.id, @user.id, new_role_ids)
513
+ API::Server.update_member(@bot.token, @server.id, @user.id, roles: new_role_ids)
514
+ end
515
+
516
+ # Server deafens this member.
517
+ def server_deafen
518
+ API::Server.update_member(@bot.token, @server.id, @user.id, deaf: true)
519
+ end
520
+
521
+ # Server undeafens this member.
522
+ def server_undeafen
523
+ API::Server.update_member(@bot.token, @server.id, @user.id, deaf: false)
524
+ end
525
+
526
+ # Server mutes this member.
527
+ def server_mute
528
+ API::Server.update_member(@bot.token, @server.id, @user.id, mute: true)
529
+ end
530
+
531
+ # Server unmutes this member.
532
+ def server_unmute
533
+ API::Server.update_member(@bot.token, @server.id, @user.id, mute: false)
386
534
  end
387
535
 
388
536
  # Sets or resets this member's nickname. Requires the Change Nickname permission for the bot itself and Manage
@@ -392,7 +540,11 @@ module Discordrb
392
540
  # Discord uses the empty string to signify 'no nickname' so we convert nil into that
393
541
  nick ||= ''
394
542
 
395
- API.change_nickname(@bot.token, @server.id, @user.id, nick)
543
+ if @user.current_bot?
544
+ API::User.change_own_nickname(@bot.token, @server.id, nick)
545
+ else
546
+ API.change_nickname(@bot.token, @server.id, @user.id, nick)
547
+ end
396
548
  end
397
549
 
398
550
  alias_method :nickname=, :nick=
@@ -418,17 +570,6 @@ module Discordrb
418
570
  @nick = nick
419
571
  end
420
572
 
421
- # Update this member's voice state
422
- # @note For internal use only.
423
- # @!visibility private
424
- def update_voice_state(channel, mute, deaf, self_mute, self_deaf)
425
- @voice_channel = channel
426
- @mute = mute
427
- @deaf = deaf
428
- @self_mute = self_mute
429
- @self_deaf = self_deaf
430
- end
431
-
432
573
  include PermissionCalculator
433
574
 
434
575
  # Overwriting inspect for debug purposes
@@ -446,6 +587,12 @@ module Discordrb
446
587
  [role.resolve_id]
447
588
  end
448
589
  end
590
+
591
+ # Utility method to get data out of this member's voice state
592
+ def voice_state_attribute(name)
593
+ voice_state = @server.voice_states[@user.id]
594
+ voice_state.send name if voice_state
595
+ end
449
596
  end
450
597
 
451
598
  # Recipients are members on private channels - they exist for completeness purposes, but all
@@ -479,13 +626,11 @@ module Discordrb
479
626
  end
480
627
  end
481
628
 
482
- # This class is a special variant of User that represents the bot's user profile (things like email addresses and the avatar).
629
+ # This class is a special variant of User that represents the bot's user profile (things like own username and the avatar).
483
630
  # It can be accessed using {Bot#profile}.
484
631
  class Profile < User
485
- def initialize(data, bot, email, password)
632
+ def initialize(data, bot)
486
633
  super(data, bot)
487
- @email = email
488
- @password = password
489
634
  end
490
635
 
491
636
  # Whether or not the user is the bot. The Profile can only ever be the bot user, so this always returns true.
@@ -500,22 +645,11 @@ module Discordrb
500
645
  update_profile_data(username: username)
501
646
  end
502
647
 
503
- # Sets the bot's email address. If you use this method, make sure that the login email in the script matches this
504
- # one afterwards, so the bot doesn't have any trouble logging in in the future.
505
- # @param email [String] The new email address.
506
- def email=(email)
507
- update_profile_data(email: email)
508
- end
509
-
510
- # Changes the bot's password. This will invalidate all tokens so you will have to relog the bot.
511
- # @param password [String] The new password.
512
- def password=(password)
513
- update_profile_data(new_password: password)
514
- end
648
+ alias_method :name=, :username=
515
649
 
516
650
  # Changes the bot's avatar.
517
651
  # @param avatar [String, #read] A JPG file to be used as the avatar, either
518
- # something readable (e. g. File) or as a data URL.
652
+ # something readable (e. g. File Object) or as a data URL.
519
653
  def avatar=(avatar)
520
654
  if avatar.respond_to? :read
521
655
  # Set the file to binary mode if supported, so we don't get problems with Windows
@@ -533,26 +667,22 @@ module Discordrb
533
667
  # @note For internal use only.
534
668
  # @!visibility private
535
669
  def update_data(new_data)
536
- @email = new_data[:email] || @email
537
- @password = new_data[:new_password] || @password
538
670
  @username = new_data[:username] || @username
539
671
  @avatar_id = new_data[:avatar_id] || @avatar_id
540
672
  end
541
673
 
542
674
  # The inspect method is overwritten to give more useful output
543
675
  def inspect
544
- "<Profile email=#{@email} user=#{super}>"
676
+ "<Profile user=#{super}>"
545
677
  end
546
678
 
547
679
  private
548
680
 
549
681
  def update_profile_data(new_data)
550
- API.update_user(@bot.token,
551
- new_data[:email] || @email,
552
- @password,
553
- new_data[:username] || @username,
554
- new_data[:avatar],
555
- new_data[:new_password] || nil)
682
+ API::User.update_profile(@bot.token,
683
+ nil, nil,
684
+ new_data[:username] || @username,
685
+ new_data.key?(:avatar) ? new_data[:avatar] : @avatar_id)
556
686
  update_data(new_data)
557
687
  end
558
688
  end
@@ -576,9 +706,11 @@ module Discordrb
576
706
 
577
707
  # @return [ColourRGB] the role colour
578
708
  attr_reader :colour
579
-
580
709
  alias_method :color, :colour
581
710
 
711
+ # @return [Integer] the position of this role in the hierarchy
712
+ attr_reader :position
713
+
582
714
  # This class is used internally as a wrapper to a Role object that allows easy writing of permission data.
583
715
  class RoleWriter
584
716
  # @!visibility private
@@ -602,6 +734,8 @@ module Discordrb
602
734
  @name = data['name']
603
735
  @id = data['id'].to_i
604
736
 
737
+ @position = data['position']
738
+
605
739
  @hoist = data['hoist']
606
740
  @mentionable = data['mentionable']
607
741
 
@@ -613,6 +747,14 @@ module Discordrb
613
747
  "<@&#{@id}>"
614
748
  end
615
749
 
750
+ # @return [Array<Member>] an array of members who have this role.
751
+ # @note This requests a member chunk if it hasn't for the server before, which may be slow initially
752
+ def members
753
+ @server.members.select { |m| m.role? role }
754
+ end
755
+
756
+ alias_method :users, :members
757
+
616
758
  # Updates the data cache from another Role object
617
759
  # @note For internal use only
618
760
  # @!visibility private
@@ -621,6 +763,7 @@ module Discordrb
621
763
  @name = other.name
622
764
  @hoist = other.hoist
623
765
  @colour = other.colour
766
+ @position = other.position
624
767
  end
625
768
 
626
769
  # Updates the data cache from a hash containing data
@@ -645,6 +788,12 @@ module Discordrb
645
788
  update_role_data(hoist: hoist)
646
789
  end
647
790
 
791
+ # Changes whether or not this role can be mentioned
792
+ # @param mentionable [true, false] The value it should be changed to
793
+ def mentionable=(mentionable)
794
+ update_role_data(mentionable: mentionable)
795
+ end
796
+
648
797
  # Sets the role colour to something new
649
798
  # @param colour [ColourRGB] The new colour
650
799
  def colour=(colour)
@@ -653,9 +802,16 @@ module Discordrb
653
802
 
654
803
  alias_method :color=, :colour=
655
804
 
656
- # Changes the internal packed permissions
657
- # @note For internal use only
658
- # @!visibility private
805
+ # Changes this role's permissions to a fixed bitfield. This allows setting multiple permissions at once with just
806
+ # one API call.
807
+ #
808
+ # Information on how this bitfield is structured can be found at
809
+ # https://discordapp.com/developers/docs/topics/permissions.
810
+ # @example Remove all permissions from a role
811
+ # role.packed = 0
812
+ # @param packed [Integer] A bitfield with the desired permissions value.
813
+ # @param update_perms [true, false] Whether the internal data should also be updated. This should always be true
814
+ # when calling externally.
659
815
  def packed=(packed, update_perms = true)
660
816
  update_role_data(permissions: packed)
661
817
  @permissions.bits = packed if update_perms
@@ -663,7 +819,7 @@ module Discordrb
663
819
 
664
820
  # Delets this role. This cannot be undone without recreating the role!
665
821
  def delete
666
- API.delete_role(@bot.token, @server.id, @id)
822
+ API::Server.delete_role(@bot.token, @server.id, @id)
667
823
  @server.delete_role(@id)
668
824
  end
669
825
 
@@ -675,11 +831,12 @@ module Discordrb
675
831
  private
676
832
 
677
833
  def update_role_data(new_data)
678
- API.update_role(@bot.token, @server.id, @id,
679
- new_data[:name] || @name,
680
- (new_data[:colour] || @colour).combined,
681
- new_data[:hoist].nil? ? false : !@hoist.nil?,
682
- new_data[:permissions] || @permissions.bits)
834
+ API::Server.update_role(@bot.token, @server.id, @id,
835
+ new_data[:name] || @name,
836
+ (new_data[:colour] || @colour).combined,
837
+ new_data[:hoist].nil? ? @hoist : new_data[:hoist],
838
+ new_data[:mentionable].nil? ? @mentionable : new_data[:mentionable],
839
+ new_data[:permissions] || @permissions.bits)
683
840
  update_data(new_data)
684
841
  end
685
842
  end
@@ -691,7 +848,7 @@ module Discordrb
691
848
  # @return [String] this channel's name.
692
849
  attr_reader :name
693
850
 
694
- # @return [String] this channel's type (text or voice)
851
+ # @return [Integer] this channel's type (0: text, 1: private, 2: voice, 3: group).
695
852
  attr_reader :type
696
853
 
697
854
  # @!visibility private
@@ -735,29 +892,23 @@ module Discordrb
735
892
 
736
893
  # @return [Integer] the amount of uses left on this invite.
737
894
  attr_reader :uses
895
+ alias_method :max_uses, :uses
738
896
 
739
897
  # @return [User, nil] the user that made this invite. May also be nil if the user can't be determined.
740
898
  attr_reader :inviter
899
+ alias_method :user, :inviter
741
900
 
742
901
  # @return [true, false] whether or not this invite is temporary.
743
902
  attr_reader :temporary
903
+ alias_method :temporary?, :temporary
744
904
 
745
905
  # @return [true, false] whether this invite is still valid.
746
906
  attr_reader :revoked
747
-
748
- # @return [true, false] whether this invite is in xkcd format (i. e. "Human readable" in the invite settings)
749
- attr_reader :xkcd
907
+ alias_method :revoked?, :revoked
750
908
 
751
909
  # @return [String] this invite's code
752
910
  attr_reader :code
753
911
 
754
- alias_method :max_uses, :uses
755
- alias_method :user, :inviter
756
-
757
- alias_method :temporary?, :temporary
758
- alias_method :revoked?, :revoked
759
- alias_method :xkcd?, :xkcd
760
-
761
912
  # @!visibility private
762
913
  def initialize(data, bot)
763
914
  @bot = bot
@@ -768,7 +919,6 @@ module Discordrb
768
919
  @inviter = data['inviter'] ? (@bot.user(data['inviter']['id'].to_i) || User.new(data['inviter'], bot)) : nil
769
920
  @temporary = data['temporary']
770
921
  @revoked = data['revoked']
771
- @xkcd = data['xkcdpass']
772
922
 
773
923
  @code = data['code']
774
924
  end
@@ -780,14 +930,14 @@ module Discordrb
780
930
 
781
931
  # Deletes this invite
782
932
  def delete
783
- API.delete_invite(@bot.token, @code)
933
+ API::Invite.delete(@bot.token, @code)
784
934
  end
785
935
 
786
936
  alias_method :revoke, :delete
787
937
 
788
938
  # The inspect method is overwritten to give more useful output
789
939
  def inspect
790
- "<Invite code=#{@code} channel=#{@channel} uses=#{@uses} temporary=#{@temporary} revoked=#{@revoked} xkcd=#{@xkcd}>"
940
+ "<Invite code=#{@code} channel=#{@channel} uses=#{@uses} temporary=#{@temporary} revoked=#{@revoked}>"
791
941
  end
792
942
 
793
943
  # Creates an invite URL.
@@ -798,14 +948,6 @@ module Discordrb
798
948
 
799
949
  # A Discord channel, including data like the topic
800
950
  class Channel
801
- # The type string that stands for a text channel
802
- # @see Channel#type
803
- TEXT_TYPE = 'text'.freeze
804
-
805
- # The type string that stands for a voice channel
806
- # @see Channel#type
807
- VOICE_TYPE = 'voice'.freeze
808
-
809
951
  include IDObject
810
952
 
811
953
  # @return [String] this channel's name.
@@ -814,15 +956,25 @@ module Discordrb
814
956
  # @return [Server, nil] the server this channel is on. If this channel is a PM channel, it will be nil.
815
957
  attr_reader :server
816
958
 
817
- # @return [String] the type of this channel (currently either 'text' or 'voice')
959
+ # @return [Integer] the type of this channel (0: text, 1: private, 2: voice, 3: group)
818
960
  attr_reader :type
819
961
 
820
- # @return [Recipient, nil] the recipient of the private messages, or nil if this is not a PM channel
821
- attr_reader :recipient
962
+ # @return [Integer, nil] the id of the owner of the group channel or nil if this is not a group channel.
963
+ attr_reader :owner_id
964
+
965
+ # @return [Array<Recipient>, nil] the array of recipients of the private messages, or nil if this is not a Private channel
966
+ attr_reader :recipients
822
967
 
823
968
  # @return [String] the channel's topic
824
969
  attr_reader :topic
825
970
 
971
+ # @return [Integer] the bitrate (in bps) of the channel
972
+ attr_reader :bitrate
973
+
974
+ # @return [Integer] the amount of users that can be in the channel. `0` means it is unlimited.
975
+ attr_reader :user_limit
976
+ alias_method :limit, :user_limit
977
+
826
978
  # @return [Integer] the channel's position on the channel list
827
979
  attr_reader :position
828
980
 
@@ -831,9 +983,9 @@ module Discordrb
831
983
  # @return [Hash<Integer => OpenStruct>] the channel's permission overwrites
832
984
  attr_reader :permission_overwrites
833
985
 
834
- # @return [true, false] whether or not this channel is a PM channel.
986
+ # @return [true, false] whether or not this channel is a PM or group channel.
835
987
  def private?
836
- @server.nil?
988
+ pm? || group?
837
989
  end
838
990
 
839
991
  # @return [String] a string that will mention the channel as a clickable link on Discord.
@@ -841,23 +993,38 @@ module Discordrb
841
993
  "<##{@id}>"
842
994
  end
843
995
 
996
+ # @return [Recipient, nil] the recipient of the private messages, or nil if this is not a PM channel
997
+ def recipient
998
+ @recipients.first if pm?
999
+ end
1000
+
844
1001
  # @!visibility private
845
1002
  def initialize(data, bot, server = nil)
846
1003
  @bot = bot
847
-
848
- # data is a sometimes a Hash and othertimes an array of Hashes, you only want the last one if it's an array
1004
+ # data is a sometimes a Hash and other times an array of Hashes, you only want the last one if it's an array
849
1005
  data = data[-1] if data.is_a?(Array)
850
1006
 
851
1007
  @id = data['id'].to_i
852
- @type = data['type'] || TEXT_TYPE
1008
+ @type = data['type'] || 0
853
1009
  @topic = data['topic']
1010
+ @bitrate = data['bitrate']
1011
+ @user_limit = data['user_limit']
854
1012
  @position = data['position']
855
1013
 
856
- @is_private = data['is_private']
857
- if @is_private
858
- recipient_user = bot.ensure_user(data['recipient'])
859
- @recipient = Recipient.new(recipient_user, self, bot)
860
- @name = @recipient.username
1014
+ if private?
1015
+ @recipients = []
1016
+ if data['recipients']
1017
+ data['recipients'].each do |recipient|
1018
+ recipient_user = bot.ensure_user(recipient)
1019
+ @recipients << Recipient.new(recipient_user, self, bot)
1020
+ end
1021
+ end
1022
+ if pm?
1023
+ @name = @recipients.first.username
1024
+ else
1025
+ @name = data['name']
1026
+ @owner_id = data['owner_id']
1027
+ end
861
1028
  else
862
1029
  @name = data['name']
863
1030
  @server = if server
@@ -882,12 +1049,22 @@ module Discordrb
882
1049
 
883
1050
  # @return [true, false] whether or not this channel is a text channel
884
1051
  def text?
885
- @type == TEXT_TYPE
1052
+ @type.zero?
886
1053
  end
887
1054
 
888
- # @return [true, false] whether or not this channel is a voice channel
1055
+ # @return [true, false] whether or not this channel is a PM channel.
1056
+ def pm?
1057
+ @type == 1
1058
+ end
1059
+
1060
+ # @return [true, false] whether or not this channel is a voice channel.
889
1061
  def voice?
890
- @type == VOICE_TYPE
1062
+ @type == 2
1063
+ end
1064
+
1065
+ # @return [true, false] whether or not this channel is a group channel.
1066
+ def group?
1067
+ @type == 3
891
1068
  end
892
1069
 
893
1070
  # Sends a message to this channel.
@@ -898,6 +1075,16 @@ module Discordrb
898
1075
  @bot.send_message(@id, content, tts, @server && @server.id)
899
1076
  end
900
1077
 
1078
+ alias_method :send, :send_message
1079
+
1080
+ # Sends a temporary message to this channel.
1081
+ # @param content [String] The content to send. Should not be longer than 2000 characters or it will result in an error.
1082
+ # @param timeout [Float] The amount of time in seconds after which the message sent will be deleted.
1083
+ # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
1084
+ def send_temporary_message(content, timeout, tts = false)
1085
+ @bot.send_temporary_message(@id, content, timeout, tts, @server && @server.id)
1086
+ end
1087
+
901
1088
  # Sends multiple messages to a channel
902
1089
  # @param content [Array<String>] The messages to send.
903
1090
  def send_multiple(content)
@@ -912,13 +1099,15 @@ module Discordrb
912
1099
 
913
1100
  # Sends a file to this channel. If it is an image, it will be embedded.
914
1101
  # @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)
915
- def send_file(file)
916
- @bot.send_file(@id, file)
1102
+ # @param caption [string] The caption for the file.
1103
+ # @param tts [true, false] Whether or not this file's caption should be sent using Discord text-to-speech.
1104
+ def send_file(file, caption: nil, tts: false)
1105
+ @bot.send_file(@id, file, caption: caption, tts: tts)
917
1106
  end
918
1107
 
919
1108
  # Permanently deletes this channel
920
1109
  def delete
921
- API.delete_channel(@bot.token, @id)
1110
+ API::Channel.delete(@bot.token, @id)
922
1111
  end
923
1112
 
924
1113
  # Sets this channel's name. The name must be alphanumeric with dashes, unless this is a voice channel (then there are no limitations)
@@ -931,10 +1120,29 @@ module Discordrb
931
1120
  # Sets this channel's topic.
932
1121
  # @param topic [String] The new topic.
933
1122
  def topic=(topic)
1123
+ raise 'Tried to set topic on voice channel' if voice?
934
1124
  @topic = topic
935
1125
  update_channel_data
936
1126
  end
937
1127
 
1128
+ # Sets this channel's bitrate.
1129
+ # @param bitrate [Integer] The new bitrate (in bps). Number has to be between 8000-96000 (128000 for VIP servers)
1130
+ def bitrate=(bitrate)
1131
+ raise 'Tried to set bitrate on text channel' if text?
1132
+ @bitrate = bitrate
1133
+ update_channel_data
1134
+ end
1135
+
1136
+ # Sets this channel's user limit.
1137
+ # @param limit [Integer] The new user limit. `0` for unlimited, has to be a number between 0-99
1138
+ def user_limit=(limit)
1139
+ raise 'Tried to set user_limit on text channel' if text?
1140
+ @user_limit = limit
1141
+ update_channel_data
1142
+ end
1143
+
1144
+ alias_method :limit=, :user_limit=
1145
+
938
1146
  # Sets this channel's position in the list.
939
1147
  # @param position [Integer] The new position.
940
1148
  def position=(position)
@@ -962,11 +1170,15 @@ module Discordrb
962
1170
  allow_bits = allow.respond_to?(:bits) ? allow.bits : allow
963
1171
  deny_bits = deny.respond_to?(:bits) ? deny.bits : deny
964
1172
 
965
- if thing.is_a? User
966
- API.update_user_overrides(@bot.token, @id, thing.id, allow_bits, deny_bits)
967
- elsif thing.is_a? Role
968
- API.update_role_overrides(@bot.token, @id, thing.id, allow_bits, deny_bits)
969
- end
1173
+ type = if thing.is_a?(User) || thing.is_a?(Member) || thing.is_a?(Recipient) || thing.is_a?(Profile)
1174
+ :member
1175
+ elsif thing.is_a? Role
1176
+ :role
1177
+ else
1178
+ raise ArgumentError, '`thing` in define_overwrite needs to be a kind of User (User, Member, Recipient, Profile) or a Role!'
1179
+ end
1180
+
1181
+ API::Channel.update_permission(@bot.token, @id, thing.id, allow_bits, deny_bits, type)
970
1182
  end
971
1183
 
972
1184
  # Updates the cached data from another channel.
@@ -975,20 +1187,17 @@ module Discordrb
975
1187
  def update_from(other)
976
1188
  @topic = other.topic
977
1189
  @name = other.name
978
- @recipient = other.recipient
979
1190
  @permission_overwrites = other.permission_overwrites
980
1191
  end
981
1192
 
982
- # The list of users currently in this channel. This is mostly useful for a voice channel, for a text channel it will
983
- # just return the users on the server that are online.
1193
+ # The list of users currently in this channel. For a voice channel, it will return all the members currently
1194
+ # in that channel. For a text channel, it will return all online members that have permission to read it.
984
1195
  # @return [Array<Member>] the users in this channel
985
1196
  def users
986
- if @type == 'text'
987
- @server.members.select { |u| u.status != :offline }
988
- else
989
- @server.members.select do |user|
990
- user.voice_channel.id == @id if user.voice_channel
991
- end
1197
+ if text?
1198
+ @server.online_members(include_idle: true).select { |u| u.can_read_messages? self }
1199
+ elsif voice?
1200
+ @server.voice_states.map { |id, voice_state| @server.member(id) if !voice_state.voice_channel.nil? && voice_state.voice_channel.id == @id }.compact
992
1201
  end
993
1202
  end
994
1203
 
@@ -1001,18 +1210,45 @@ module Discordrb
1001
1210
  # as soon as possible with the specified amount.
1002
1211
  # @return [Array<Message>] the retrieved messages.
1003
1212
  def history(amount, before_id = nil, after_id = nil)
1004
- logs = API.channel_log(@bot.token, @id, amount, before_id, after_id)
1213
+ logs = API::Channel.messages(@bot.token, @id, amount, before_id, after_id)
1005
1214
  JSON.parse(logs).map { |message| Message.new(message, @bot) }
1006
1215
  end
1007
1216
 
1217
+ # Retrieves message history, but only message IDs for use with prune
1218
+ # @note For internal use only
1219
+ # @!visibility private
1220
+ def history_ids(amount, before_id = nil, after_id = nil)
1221
+ logs = API::Channel.messages(@bot.token, @id, amount, before_id, after_id)
1222
+ JSON.parse(logs).map { |message| message['id'] }
1223
+ end
1224
+
1225
+ # Returns a single message from this channel's history by ID.
1226
+ # @param message_id [Integer] The ID of the message to retrieve.
1227
+ # @return [Message] the retrieved message.
1228
+ def load_message(message_id)
1229
+ response = API::Channel.message(@bot.token, @id, message_id)
1230
+ return Message.new(JSON.parse(response), @bot)
1231
+ rescue RestClient::ResourceNotFound
1232
+ return nil
1233
+ end
1234
+
1235
+ alias_method :message, :load_message
1236
+
1237
+ # Requests all pinned messages of a channel.
1238
+ # @return [Array<Message>] the received messages.
1239
+ def pins
1240
+ msgs = API::Channel.pinned_messages(@bot.token, @id)
1241
+ JSON.parse(msgs).map { |msg| Message.new(msg, @bot) }
1242
+ end
1243
+
1008
1244
  # Delete the last N messages on this channel.
1009
1245
  # @param amount [Integer] How many messages to delete. Must be a value between 2 and 100 (Discord limitation)
1010
1246
  # @raise [ArgumentError] if the amount of messages is not a value between 2 and 100
1011
1247
  def prune(amount)
1012
1248
  raise ArgumentError, 'Can only prune between 2 and 100 messages!' unless amount.between?(2, 100)
1013
1249
 
1014
- messages = history(amount).map(&:id)
1015
- API.bulk_delete(@bot.token, @id, messages)
1250
+ messages = history_ids(amount)
1251
+ API::Channel.bulk_delete_messages(@bot.token, @id, messages)
1016
1252
  end
1017
1253
 
1018
1254
  # Updates the cached permission overwrites
@@ -1033,33 +1269,214 @@ module Discordrb
1033
1269
  # @param max_age [Integer] How many seconds this invite should last.
1034
1270
  # @param max_uses [Integer] How many times this invite should be able to be used.
1035
1271
  # @param temporary [true, false] Whether membership should be temporary (kicked after going offline).
1036
- # @param xkcd [true, false] Whether or not the invite should be human-readable.
1037
1272
  # @return [Invite] the created invite.
1038
- def make_invite(max_age = 0, max_uses = 0, temporary = false, xkcd = false)
1039
- response = API.create_invite(@bot.token, @id, max_age, max_uses, temporary, xkcd)
1273
+ def make_invite(max_age = 0, max_uses = 0, temporary = false)
1274
+ response = API::Channel.create_invite(@bot.token, @id, max_age, max_uses, temporary)
1040
1275
  Invite.new(JSON.parse(response), @bot)
1041
1276
  end
1042
1277
 
1278
+ alias_method :invite, :make_invite
1279
+
1043
1280
  # Starts typing, which displays the typing indicator on the client for five seconds.
1044
1281
  # If you want to keep typing you'll have to resend this every five seconds. (An abstraction
1045
1282
  # for this will eventually be coming)
1046
1283
  def start_typing
1047
- API.start_typing(@bot.token, @id)
1284
+ API::Channel.start_typing(@bot.token, @id)
1048
1285
  end
1049
1286
 
1050
- alias_method :send, :send_message
1051
- alias_method :message, :send_message
1052
- alias_method :invite, :make_invite
1287
+ # Creates a Group channel
1288
+ # @param user_ids [Array<Integer>] Array of user IDs to add to the new group channel (Excluding
1289
+ # the recipient of the PM channel).
1290
+ # @return [Channel] the created channel.
1291
+ def create_group(user_ids)
1292
+ raise 'Attempted to create group channel on a non-pm channel!' unless pm?
1293
+ response = API::Channel.create_group(@bot.token, @id, user_ids.shift)
1294
+ channel = Channel.new(JSON.parse(response), @bot)
1295
+ channel.add_group_users(user_ids)
1296
+ end
1297
+
1298
+ # Adds a user to a Group channel
1299
+ # @param user_ids [Array<#resolve_id>, #resolve_id] User ID or array of user IDs to add to the group channel.
1300
+ # @return [Channel] the group channel.
1301
+ def add_group_users(user_ids)
1302
+ raise 'Attempted to add a user to a non-group channel!' unless group?
1303
+ user_ids = [user_ids] unless user_ids.is_a? Array
1304
+ user_ids.each do |user_id|
1305
+ API::Channel.add_group_user(@bot.token, @id, user_id.resolve_id)
1306
+ end
1307
+ self
1308
+ end
1309
+
1310
+ alias_method :add_group_user, :add_group_users
1311
+
1312
+ # Removes a user from a group channel.
1313
+ # @param user_ids [Array<#resolve_id>, #resolve_id] User ID or array of user IDs to remove from the group channel.
1314
+ # @return [Channel] the group channel.
1315
+ def remove_group_users(user_ids)
1316
+ raise 'Attempted to remove a user from a non-group channel!' unless group?
1317
+ user_ids = [user_ids] unless user_ids.is_a? Array
1318
+ user_ids.each do |user_id|
1319
+ API::Channel.remove_group_user(@bot.token, @id, user_id.resolve_id)
1320
+ end
1321
+ self
1322
+ end
1323
+
1324
+ alias_method :remove_group_user, :remove_group_users
1325
+
1326
+ # Leaves the group
1327
+ def leave_group
1328
+ raise 'Attempted to leave a non-group channel!' unless group?
1329
+ API::Channel.leave_group(@bot.token, @id)
1330
+ end
1331
+
1332
+ alias_method :leave, :leave_group
1053
1333
 
1054
1334
  # The inspect method is overwritten to give more useful output
1055
1335
  def inspect
1056
1336
  "<Channel name=#{@name} id=#{@id} topic=\"#{@topic}\" type=#{@type} position=#{@position} server=#{@server}>"
1057
1337
  end
1058
1338
 
1339
+ # Adds a recipient to a group channel.
1340
+ # @param recipient [Recipient] the recipient to add to the group
1341
+ # @raise [ArgumentError] if tried to add a non-recipient
1342
+ # @note For internal use only
1343
+ # @!visibility private
1344
+ def add_recipient(recipient)
1345
+ raise 'Tried to add recipient to a non-group channel' unless group?
1346
+ raise ArgumentError, 'Tried to add a non-recipient to a group' unless recipient.is_a?(Recipient)
1347
+ @recipients << recipient
1348
+ end
1349
+
1350
+ # Removes a recipient from a group channel.
1351
+ # @param recipient [Recipient] the recipient to remove from the group
1352
+ # @raise [ArgumentError] if tried to remove a non-recipient
1353
+ # @note For internal use only
1354
+ # @!visibility private
1355
+ def remove_recipient(recipient)
1356
+ raise 'Tried to add recipient to a non-group channel' unless group?
1357
+ raise ArgumentError, 'Tried to remove a non-recipient from a group' unless recipient.is_a?(Recipient)
1358
+ @recipients.delete(recipient)
1359
+ end
1360
+
1059
1361
  private
1060
1362
 
1061
1363
  def update_channel_data
1062
- API.update_channel(@bot.token, @id, @name, @topic, @position)
1364
+ API.update_channel(@bot.token, @id, @name, @topic, @position, @bitrate, @user_limit)
1365
+ end
1366
+ end
1367
+
1368
+ # An Embed object that is contained in a message
1369
+ # A freshly generated embed object will not appear in a message object
1370
+ # unless grabbed from its ID in a channel.
1371
+ class Embed
1372
+ # @return [Message] the message this embed object is contained in.
1373
+ attr_reader :message
1374
+
1375
+ # @return [String] the URL this embed object is based on.
1376
+ attr_reader :url
1377
+
1378
+ # @return [String, nil] the title of the embed object. `nil` if there is not a title
1379
+ attr_reader :title
1380
+
1381
+ # @return [String, nil] the description of the embed object. `nil` if there is not a description
1382
+ attr_reader :description
1383
+
1384
+ # @return [Symbol] the type of the embed object. Possible types are:
1385
+ #
1386
+ # * `:link`
1387
+ # * `:video`
1388
+ # * `:image`
1389
+ attr_reader :type
1390
+
1391
+ # @return [EmbedProvider, nil] the provider of the embed object. `nil` is there is not a provider
1392
+ attr_reader :provider
1393
+
1394
+ # @return [EmbedThumbnail, nil] the thumbnail of the embed object. `nil` is there is not a thumbnail
1395
+ attr_reader :thumbnail
1396
+
1397
+ # @return [EmbedAuthor, nil] the author of the embed object. `nil` is there is not an author
1398
+ attr_reader :author
1399
+
1400
+ # @!visibility private
1401
+ def initialize(data, message)
1402
+ @message = message
1403
+
1404
+ @url = data['url']
1405
+ @title = data['title']
1406
+ @type = data['type'].to_sym
1407
+ @description = data['description']
1408
+ @provider = data['provider'].nil? ? nil : EmbedProvider.new(data['provider'], self)
1409
+ @thumbnail = data['thumbnail'].nil? ? nil : EmbedThumbnail.new(data['thumbnail'], self)
1410
+ @author = data['author'].nil? ? nil : EmbedAuthor.new(data['author'], self)
1411
+ end
1412
+ end
1413
+
1414
+ # An Embed thumbnail for the embed object
1415
+ class EmbedThumbnail
1416
+ # @return [Embed] the embed object this is based on.
1417
+ attr_reader :embed
1418
+
1419
+ # @return [String] the CDN URL this thumbnail can be downloaded at.
1420
+ attr_reader :url
1421
+
1422
+ # @return [String] the thumbnail's proxy URL - I'm not sure what exactly this does, but I think it has something to
1423
+ # do with CDNs
1424
+ attr_reader :proxy_url
1425
+
1426
+ # @return [Integer] the width of this thumbnail file, in pixels.
1427
+ attr_reader :width
1428
+
1429
+ # @return [Integer] the height of this thumbnail file, in pixels.
1430
+ attr_reader :height
1431
+
1432
+ # @!visibility private
1433
+ def initialize(data, embed)
1434
+ @embed = embed
1435
+
1436
+ @url = data['url']
1437
+ @proxy_url = data['proxy_url']
1438
+ @width = data['width']
1439
+ @height = data['height']
1440
+ end
1441
+ end
1442
+
1443
+ # An Embed provider for the embed object
1444
+ class EmbedProvider
1445
+ # @return [Embed] the embed object this is based on.
1446
+ attr_reader :embed
1447
+
1448
+ # @return [String] the provider's name.
1449
+ attr_reader :name
1450
+
1451
+ # @return [String, nil] the URL of the provider. `nil` is there is no URL
1452
+ attr_reader :url
1453
+
1454
+ # @!visibility private
1455
+ def initialize(data, embed)
1456
+ @embed = embed
1457
+
1458
+ @name = data['name']
1459
+ @url = data['url']
1460
+ end
1461
+ end
1462
+
1463
+ # An Embed author for the embed object
1464
+ class EmbedAuthor
1465
+ # @return [Embed] the embed object this is based on.
1466
+ attr_reader :embed
1467
+
1468
+ # @return [String] the author's name.
1469
+ attr_reader :name
1470
+
1471
+ # @return [String, nil] the URL of the author's website. `nil` is there is no URL
1472
+ attr_reader :url
1473
+
1474
+ # @!visibility private
1475
+ def initialize(data, embed)
1476
+ @embed = embed
1477
+
1478
+ @name = data['name']
1479
+ @url = data['url']
1063
1480
  end
1064
1481
  end
1065
1482
 
@@ -1116,9 +1533,13 @@ module Discordrb
1116
1533
 
1117
1534
  # @return [String] the content of this message.
1118
1535
  attr_reader :content
1536
+ alias_method :text, :content
1537
+ alias_method :to_s, :content
1119
1538
 
1120
1539
  # @return [Member] the user that sent this message.
1121
1540
  attr_reader :author
1541
+ alias_method :user, :author
1542
+ alias_method :writer, :author
1122
1543
 
1123
1544
  # @return [Channel] the channel in which this message was sent.
1124
1545
  attr_reader :channel
@@ -1126,6 +1547,10 @@ module Discordrb
1126
1547
  # @return [Time] the timestamp at which this message was sent.
1127
1548
  attr_reader :timestamp
1128
1549
 
1550
+ # @return [Time] the timestamp at which this message was edited. `nil` if the message was never edited.
1551
+ attr_reader :edited_timestamp
1552
+ alias_method :edit_timestamp, :edited_timestamp
1553
+
1129
1554
  # @return [Array<User>] the users that were mentioned in this message.
1130
1555
  attr_reader :mentions
1131
1556
 
@@ -1135,15 +1560,38 @@ module Discordrb
1135
1560
  # @return [Array<Attachment>] the files attached to this message.
1136
1561
  attr_reader :attachments
1137
1562
 
1138
- alias_method :user, :author
1139
- alias_method :text, :content
1140
- alias_method :to_s, :content
1563
+ # @return [Array<Embed>] the embed objects contained in this message.
1564
+ attr_reader :embeds
1565
+
1566
+ # @return [true, false] whether the message used Text-To-Speech (TTS) or not.
1567
+ attr_reader :tts
1568
+ alias_method :tts?, :tts
1569
+
1570
+ # @return [String] used for validating a message was sent
1571
+ attr_reader :nonce
1572
+
1573
+ # @return [true, false] whether the message was edited or not.
1574
+ attr_reader :edited
1575
+ alias_method :edited?, :edited
1576
+
1577
+ # @return [true, false] whether the message mentioned everyone or not.
1578
+ attr_reader :mention_everyone
1579
+ alias_method :mention_everyone?, :mention_everyone
1580
+ alias_method :mentions_everyone?, :mention_everyone
1581
+
1582
+ # @return [true, false] whether the message is pinned or not.
1583
+ attr_reader :pinned
1584
+ alias_method :pinned?, :pinned
1141
1585
 
1142
1586
  # @!visibility private
1143
1587
  def initialize(data, bot)
1144
1588
  @bot = bot
1145
1589
  @content = data['content']
1146
1590
  @channel = bot.channel(data['channel_id'].to_i)
1591
+ @pinned = data['pinned']
1592
+ @tts = data['tts']
1593
+ @nonce = data['nonce']
1594
+ @mention_everyone = data['mention_everyone']
1147
1595
 
1148
1596
  @author = if data['author']
1149
1597
  if @channel.private?
@@ -1151,13 +1599,15 @@ module Discordrb
1151
1599
  # directly because the bot may also send messages to the channel
1152
1600
  Recipient.new(bot.user(data['author']['id'].to_i), @channel, bot)
1153
1601
  else
1154
- member = @channel.server.member(data['author']['id'].to_i, false)
1602
+ member = @channel.server.member(data['author']['id'].to_i)
1155
1603
  Discordrb::LOGGER.warn("Member with ID #{data['author']['id']} not cached even though it should be.") unless member
1156
1604
  member
1157
1605
  end
1158
1606
  end
1159
1607
 
1160
1608
  @timestamp = Time.parse(data['timestamp']) if data['timestamp']
1609
+ @edited_timestamp = data['edited_timestamp'].nil? ? nil : Time.parse(data['edited_timestamp'])
1610
+ @edited = !@edited_timestamp.nil?
1161
1611
  @id = data['id'].to_i
1162
1612
 
1163
1613
  @mentions = []
@@ -1169,7 +1619,7 @@ module Discordrb
1169
1619
  @role_mentions = []
1170
1620
 
1171
1621
  # Role mentions can only happen on public servers so make sure we only parse them there
1172
- unless @channel.private?
1622
+ if @channel.text?
1173
1623
  data['mention_roles'].each do |element|
1174
1624
  @role_mentions << @channel.server.role(element.to_i)
1175
1625
  end if data['mention_roles']
@@ -1177,6 +1627,9 @@ module Discordrb
1177
1627
 
1178
1628
  @attachments = []
1179
1629
  @attachments = data['attachments'].map { |e| Attachment.new(e, self, @bot) } if data['attachments']
1630
+
1631
+ @embeds = []
1632
+ @embeds = data['embeds'].map { |e| Embed.new(e, self) } if data['embeds']
1180
1633
  end
1181
1634
 
1182
1635
  # Replies to this message with the specified content.
@@ -1189,13 +1642,27 @@ module Discordrb
1189
1642
  # @param new_content [String] the new content the message should have.
1190
1643
  # @return [Message] the resulting message.
1191
1644
  def edit(new_content)
1192
- response = API.edit_message(@bot.token, @channel.id, @id, new_content)
1645
+ response = API::Channel.edit_message(@bot.token, @channel.id, @id, new_content)
1193
1646
  Message.new(JSON.parse(response), @bot)
1194
1647
  end
1195
1648
 
1196
1649
  # Deletes this message.
1197
1650
  def delete
1198
- API.delete_message(@bot.token, @channel.id, @id)
1651
+ API::Channel.delete_message(@bot.token, @channel.id, @id)
1652
+ nil
1653
+ end
1654
+
1655
+ # Pins this message
1656
+ def pin
1657
+ API::Channel.pin_message(@bot.token, @channel.id, @id)
1658
+ @pinned = true
1659
+ nil
1660
+ end
1661
+
1662
+ # Unpins this message
1663
+ def unpin
1664
+ API::Channel.unpin_message(@bot.token, @channel.id, @id)
1665
+ @pinned = false
1199
1666
  nil
1200
1667
  end
1201
1668
 
@@ -1227,10 +1694,91 @@ module Discordrb
1227
1694
  # Utility function to get the URL for the icon image
1228
1695
  # @return [String] the URL to the icon image
1229
1696
  def icon_url
1697
+ return nil unless @icon_id
1230
1698
  API.icon_url(@id, @icon_id)
1231
1699
  end
1232
1700
  end
1233
1701
 
1702
+ # Integration Account
1703
+ class IntegrationAccount
1704
+ # @return [String] this accounts's name.
1705
+ attr_reader :name
1706
+
1707
+ # @return [Integer] this account's ID.
1708
+ attr_reader :id
1709
+
1710
+ def initialize(data)
1711
+ @name = data['name']
1712
+ @id = data['id'].to_i
1713
+ end
1714
+ end
1715
+
1716
+ # Server integration
1717
+ class Integration
1718
+ include IDObject
1719
+
1720
+ # @return [String] the integration name
1721
+ attr_reader :name
1722
+
1723
+ # @return [Server] the server the integration is linked to
1724
+ attr_reader :server
1725
+
1726
+ # @return [User] the user the integration is linked to
1727
+ attr_reader :user
1728
+
1729
+ # @return [Role, nil] the role that this integration uses for "subscribers"
1730
+ attr_reader :role
1731
+
1732
+ # @return [true, false] whether emoticons are enabled
1733
+ attr_reader :emoticon
1734
+ alias_method :emoticon?, :emoticon
1735
+
1736
+ # @return [String] the integration type (Youtube, Twitch, etc.)
1737
+ attr_reader :type
1738
+
1739
+ # @return [true, false] whether the integration is enabled
1740
+ attr_reader :enabled
1741
+
1742
+ # @return [true, false] whether the integration is syncing
1743
+ attr_reader :syncing
1744
+
1745
+ # @return [IntegrationAccount] the integration account information
1746
+ attr_reader :account
1747
+
1748
+ # @return [Time] the time the integration was synced at
1749
+ attr_reader :synced_at
1750
+
1751
+ # @return [Symbol] the behaviour of expiring subscribers (:remove = Remove User from role; :kick = Kick User from server)
1752
+ attr_reader :expire_behaviour
1753
+ alias_method :expire_behavior, :expire_behaviour
1754
+
1755
+ # @return [Integer] the grace period before subscribers expire (in days)
1756
+ attr_reader :expire_grace_period
1757
+
1758
+ def initialize(data, bot, server)
1759
+ @bot = bot
1760
+
1761
+ @name = data['name']
1762
+ @server = server
1763
+ @id = data['id'].to_i
1764
+ @enabled = data['enabled']
1765
+ @syncing = data['syncing']
1766
+ @type = data['type']
1767
+ @account = IntegrationAccount.new(data['account'])
1768
+ @synced_at = Time.parse(data['synced_at'])
1769
+ @expire_behaviour = [:remove, :kick][data['expire_behavior']]
1770
+ @expire_grace_period = data['expire_grace_period']
1771
+ @user = @bot.ensure_user(data['user'])
1772
+ @role = server.role(data['role_id']) || nil
1773
+ @emoticon = data['enable_emoticons']
1774
+ end
1775
+
1776
+ # The inspect method is overwritten to give more useful output
1777
+ def inspect
1778
+ "<Integration name=#{@name} id=#{@id} type=#{@type} enabled=#{@enabled}>"
1779
+ end
1780
+ end
1781
+
1234
1782
  # A server on Discord
1235
1783
  class Server
1236
1784
  include IDObject
@@ -1253,15 +1801,24 @@ module Discordrb
1253
1801
  attr_reader :large
1254
1802
  alias_method :large?, :large
1255
1803
 
1804
+ # @return [Array<Symbol>] the features of the server (eg. "INVITE_SPLASH")
1805
+ attr_reader :features
1806
+
1256
1807
  # @return [Integer] the absolute number of members on this server, offline or not.
1257
1808
  attr_reader :member_count
1258
1809
 
1810
+ # @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').
1811
+ attr_reader :verification_level
1812
+
1259
1813
  # @return [Integer] the amount of time after which a voice user gets moved into the AFK channel, in seconds.
1260
1814
  attr_reader :afk_timeout
1261
1815
 
1262
1816
  # @return [Channel, nil] the AFK voice channel of this server, or nil if none is set
1263
1817
  attr_reader :afk_channel
1264
1818
 
1819
+ # @return [Hash<Integer => VoiceState>] the hash (user ID => voice state) of voice states of members on this server
1820
+ attr_reader :voice_states
1821
+
1265
1822
  # @!visibility private
1266
1823
  def initialize(data, bot, exists = true)
1267
1824
  @bot = bot
@@ -1271,7 +1828,12 @@ module Discordrb
1271
1828
 
1272
1829
  @large = data['large']
1273
1830
  @member_count = data['member_count']
1831
+ @verification_level = [:none, :low, :medium, :high][data['verification_level']]
1832
+ @splash_id = nil
1833
+ @embed = nil
1834
+ @features = data['features'].map { |element| element.downcase.to_sym }
1274
1835
  @members = {}
1836
+ @voice_states = {}
1275
1837
 
1276
1838
  process_roles(data['roles'])
1277
1839
  process_members(data['members'])
@@ -1308,7 +1870,7 @@ module Discordrb
1308
1870
  return @members[id] if member_cached?(id)
1309
1871
  return nil unless request
1310
1872
 
1311
- member = @bot.member(@id, id)
1873
+ member = @bot.member(self, id)
1312
1874
  @members[id] = member
1313
1875
  rescue
1314
1876
  nil
@@ -1326,6 +1888,25 @@ module Discordrb
1326
1888
 
1327
1889
  alias_method :users, :members
1328
1890
 
1891
+ # @return [Array<Integration>] an array of all the integrations connected to this server.
1892
+ def integrations
1893
+ integration = JSON.parse(API.server_integrations(@bot.token, @id))
1894
+ integration.map { |element| Integration.new(element, @bot, self) }
1895
+ end
1896
+
1897
+ # Cache @embed
1898
+ # @note For internal use only
1899
+ # @!visibility private
1900
+ def cache_embed
1901
+ @embed = JSON.parse(API.server(@bot.token, @id))['embed_enabled'] if @embed.nil?
1902
+ end
1903
+
1904
+ # @return [true, false] whether or not the server has widget enabled
1905
+ def embed?
1906
+ cache_embed if @embed.nil?
1907
+ @embed
1908
+ end
1909
+
1329
1910
  # @param include_idle [true, false] Whether to count idle members as online.
1330
1911
  # @param include_bots [true, false] Whether to include bot accounts in the count.
1331
1912
  # @return [Array<Member>] an array of online members on this server.
@@ -1347,6 +1928,42 @@ module Discordrb
1347
1928
  @channels.select(&:voice?)
1348
1929
  end
1349
1930
 
1931
+ # @return [String, nil] the widget URL to the server that displays the amount of online members in a
1932
+ # stylish way. `nil` if the widget is not enabled.
1933
+ def widget_url
1934
+ cache_embed if @embed.nil?
1935
+ return nil unless @embed
1936
+ API.widget_url(@id)
1937
+ end
1938
+
1939
+ # @param style [Symbol] The style the picture should have. Possible styles are:
1940
+ # * `:banner1` creates a rectangular image with the server name, member count and icon, a "Powered by Discord" message on the bottom and an arrow on the right.
1941
+ # * `:banner2` creates a less tall rectangular image that has the same information as `banner1`, but the Discord logo on the right - together with the arrow and separated by a diagonal separator.
1942
+ # * `:banner3` creates an image similar in size to `banner1`, but it has the arrow in the bottom part, next to the Discord logo and with a "Chat now" text.
1943
+ # * `:banner4` creates a tall, almost square, image that prominently features the Discord logo at the top and has a "Join my server" in a pill-style button on the bottom. The information about the server is in the same format as the other three `banner` styles.
1944
+ # * `:shield` creates a very small, long rectangle, of the style you'd find at the top of GitHub `README.md` files. It features a small version of the Discord logo at the left and the member count at the right.
1945
+ # @return [String, nil] the widget banner URL to the server that displays the amount of online members,
1946
+ # server icon and server name in a stylish way. `nil` if the widget is not enabled.
1947
+ def widget_banner_url(style)
1948
+ return nil unless @embed
1949
+ cache_embed if @embed.nil?
1950
+ API.widget_url(@id, style)
1951
+ end
1952
+
1953
+ # @return [String] the hexadecimal ID used to identify this server's splash image for their VIP invite page.
1954
+ def splash_id
1955
+ @splash_id = JSON.parse(API.server(@bot.token, @id))['splash'] if @splash_id.nil?
1956
+ @splash_id
1957
+ end
1958
+
1959
+ # @return [String, nil] the splash image URL for the server's VIP invite page.
1960
+ # `nil` if there is no splash image.
1961
+ def splash_url
1962
+ splash_id if @splash_id.nil?
1963
+ return nil unless @splash_id
1964
+ API.splash_url(@id, @splash_id)
1965
+ end
1966
+
1350
1967
  # Adds a role to the role cache
1351
1968
  # @note For internal use only
1352
1969
  # @!visibility private
@@ -1399,10 +2016,41 @@ module Discordrb
1399
2016
  @members[member.id] = member
1400
2017
  end
1401
2018
 
2019
+ # Updates a member's voice state
2020
+ # @note For internal use only
2021
+ # @!visibility private
2022
+ def update_voice_state(data)
2023
+ user_id = data['user_id'].to_i
2024
+
2025
+ if data['channel_id']
2026
+ unless @voice_states[user_id]
2027
+ # Create a new voice state for the user
2028
+ @voice_states[user_id] = VoiceState.new(user_id)
2029
+ end
2030
+
2031
+ # Update the existing voice state (or the one we just created)
2032
+ channel = @channels_by_id[data['channel_id'].to_i]
2033
+ @voice_states[user_id].update(
2034
+ channel,
2035
+ data['mute'],
2036
+ data['deaf'],
2037
+ data['self_mute'],
2038
+ data['self_deaf']
2039
+ )
2040
+ else
2041
+ # The user is not in a voice channel anymore, so delete its voice state
2042
+ @voice_states.delete(user_id)
2043
+ end
2044
+ end
2045
+
1402
2046
  # Creates a channel on this server with the given name.
2047
+ # @param name [String] Name of the channel to create
2048
+ # @param type [Integer] Type of channel to create (0: text, 2: voice)
1403
2049
  # @return [Channel] the created channel.
1404
- def create_channel(name, type = 'text')
1405
- response = API.create_channel(@bot.token, @id, name, type)
2050
+ # @raise [ArgumentError] if type is not 0 or 2
2051
+ def create_channel(name, type = 0)
2052
+ raise ArgumentError, 'Channel type must be either 0 (text) or 2 (voice)!' unless [0, 2].include?(type)
2053
+ response = API::Server.create_channel(@bot.token, @id, name, type)
1406
2054
  Channel.new(JSON.parse(response), @bot)
1407
2055
  end
1408
2056
 
@@ -1411,7 +2059,7 @@ module Discordrb
1411
2059
  # colour is the default etc.
1412
2060
  # @return [Role] the created role.
1413
2061
  def create_role
1414
- response = API.create_role(@bot.token, @id)
2062
+ response = API::Server.create_role(@bot.token, @id)
1415
2063
  role = Role.new(JSON.parse(response), @bot, self)
1416
2064
  @roles << role
1417
2065
  role
@@ -1419,7 +2067,7 @@ module Discordrb
1419
2067
 
1420
2068
  # @return [Array<User>] a list of banned users on this server.
1421
2069
  def bans
1422
- users = JSON.parse(API.bans(@bot.token, @id))
2070
+ users = JSON.parse(API::Server.bans(@bot.token, @id))
1423
2071
  users.map { |e| User.new(e['user'], @bot) }
1424
2072
  end
1425
2073
 
@@ -1427,42 +2075,42 @@ module Discordrb
1427
2075
  # @param user [User, #resolve_id] The user to ban.
1428
2076
  # @param message_days [Integer] How many days worth of messages sent by the user should be deleted.
1429
2077
  def ban(user, message_days = 0)
1430
- API.ban_user(@bot.token, @id, user.resolve_id, message_days)
2078
+ API::Server.ban_user(@bot.token, @id, user.resolve_id, message_days)
1431
2079
  end
1432
2080
 
1433
2081
  # Unbans a previously banned user from this server.
1434
2082
  # @param user [User, #resolve_id] The user to unban.
1435
2083
  def unban(user)
1436
- API.unban_user(@bot.token, @id, user.resolve_id)
2084
+ API::Server.unban_user(@bot.token, @id, user.resolve_id)
1437
2085
  end
1438
2086
 
1439
2087
  # Kicks a user from this server.
1440
2088
  # @param user [User, #resolve_id] The user to kick.
1441
2089
  def kick(user)
1442
- API.kick_user(@bot.token, @id, user.resolve_id)
2090
+ API::Server.remove_member(@bot.token, @id, user.resolve_id)
1443
2091
  end
1444
2092
 
1445
2093
  # Forcibly moves a user into a different voice channel. Only works if the bot has the permission needed.
1446
2094
  # @param user [User] The user to move.
1447
2095
  # @param channel [Channel] The voice channel to move into.
1448
2096
  def move(user, channel)
1449
- API.move_user(@bot.token, @id, user.id, channel.id)
2097
+ API::Server.update_member(@bot.token, @id, user.id, channel_id: channel.id)
1450
2098
  end
1451
2099
 
1452
2100
  # Deletes this server. Be aware that this is permanent and impossible to undo, so be careful!
1453
2101
  def delete
1454
- API.delete_server(@bot.token, @id)
2102
+ API::Server.delete(@bot.token, @id)
1455
2103
  end
1456
2104
 
1457
2105
  # Leave the server
1458
2106
  def leave
1459
- API.leave_server(@bot.token, @id)
2107
+ API::User.leave_server(@bot.token, @id)
1460
2108
  end
1461
2109
 
1462
2110
  # Transfers server ownership to another user.
1463
2111
  # @param user [User] The user who should become the new owner.
1464
2112
  def owner=(user)
1465
- API.transfer_ownership(@bot.token, @id, user.id)
2113
+ API::Server.transfer_ownership(@bot.token, @id, user.id)
1466
2114
  end
1467
2115
 
1468
2116
  # Sets the server's name.
@@ -1529,7 +2177,13 @@ module Discordrb
1529
2177
  @afk_timeout = new_data[:afk_timeout] || new_data['afk_timeout'].to_i || @afk_timeout
1530
2178
 
1531
2179
  @afk_channel_id = new_data[:afk_channel_id] || new_data['afk_channel_id'].to_i || @afk_channel.id
1532
- @afk_channel = @bot.channel(@afk_channel_id, self) if @afk_channel_id != 0 && (!@afk_channel || @afk_channel_id != @afk_channel.id)
2180
+
2181
+ begin
2182
+ @afk_channel = @bot.channel(@afk_channel_id, self) if @afk_channel_id.nonzero? && (!@afk_channel || @afk_channel_id != @afk_channel.id)
2183
+ rescue Discordrb::Errors::NoPermission
2184
+ LOGGER.debug("AFK channel #{@afk_channel_id} on server #{@id} is unreachable, setting to nil even though one exists")
2185
+ @afk_channel = nil
2186
+ end
1533
2187
  end
1534
2188
 
1535
2189
  # The inspect method is overwritten to give more useful output
@@ -1540,12 +2194,12 @@ module Discordrb
1540
2194
  private
1541
2195
 
1542
2196
  def update_server_data(new_data)
1543
- API.update_server(@bot.token, @id,
1544
- new_data[:name] || @name,
1545
- new_data[:region] || @region,
1546
- new_data[:icon_id] || @icon_id,
1547
- new_data[:afk_channel_id] || @afk_channel_id,
1548
- new_data[:afk_timeout] || @afk_timeout)
2197
+ API::Server.update(@bot.token, @id,
2198
+ new_data[:name] || @name,
2199
+ new_data[:region] || @region,
2200
+ new_data[:icon_id] || @icon_id,
2201
+ new_data[:afk_channel_id] || @afk_channel_id,
2202
+ new_data[:afk_timeout] || @afk_timeout)
1549
2203
  update_data(new_data)
1550
2204
  end
1551
2205
 
@@ -1566,17 +2220,6 @@ module Discordrb
1566
2220
  return unless members
1567
2221
  members.each do |element|
1568
2222
  member = Member.new(element, self, @bot)
1569
- if @members[member.id] && @members[member.id].voice_channel
1570
- @bot.debug("Preserving voice state of member #{member.id} while chunking")
1571
- old_member = @members[member.id]
1572
- member.update_voice_state(
1573
- old_member.voice_channel,
1574
- old_member.mute,
1575
- old_member.deaf,
1576
- old_member.self_mute,
1577
- old_member.self_deaf
1578
- )
1579
- end
1580
2223
  @members[member.id] = member
1581
2224
  end
1582
2225
  end
@@ -1591,6 +2234,8 @@ module Discordrb
1591
2234
  if user
1592
2235
  user.status = element['status'].to_sym
1593
2236
  user.game = element['game'] ? element['game']['name'] : nil
2237
+ else
2238
+ LOGGER.warn "Rogue presence update! #{element['user']['id']} on #{@id}"
1594
2239
  end
1595
2240
  end
1596
2241
  end
@@ -1610,18 +2255,7 @@ module Discordrb
1610
2255
  def process_voice_states(voice_states)
1611
2256
  return unless voice_states
1612
2257
  voice_states.each do |element|
1613
- user_id = element['user_id'].to_i
1614
- member = @members[user_id]
1615
- next unless member
1616
- channel_id = element['channel_id'].to_i
1617
- channel = channel_id ? @channels_by_id[channel_id] : nil
1618
-
1619
- member.update_voice_state(
1620
- channel,
1621
- element['mute'],
1622
- element['deaf'],
1623
- element['self_mute'],
1624
- element['self_deaf'])
2258
+ update_voice_state(element)
1625
2259
  end
1626
2260
  end
1627
2261
  end