discordrb 1.8.1 → 2.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.

Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.overcommit.yml +7 -0
  3. data/.rubocop.yml +5 -4
  4. data/CHANGELOG.md +77 -0
  5. data/README.md +25 -15
  6. data/discordrb.gemspec +2 -3
  7. data/examples/commands.rb +14 -2
  8. data/examples/ping.rb +1 -1
  9. data/examples/pm_send.rb +1 -1
  10. data/lib/discordrb.rb +9 -0
  11. data/lib/discordrb/api.rb +176 -50
  12. data/lib/discordrb/await.rb +3 -0
  13. data/lib/discordrb/bot.rb +607 -372
  14. data/lib/discordrb/cache.rb +208 -0
  15. data/lib/discordrb/commands/command_bot.rb +50 -18
  16. data/lib/discordrb/commands/container.rb +11 -2
  17. data/lib/discordrb/commands/events.rb +2 -0
  18. data/lib/discordrb/commands/parser.rb +10 -8
  19. data/lib/discordrb/commands/rate_limiter.rb +2 -0
  20. data/lib/discordrb/container.rb +24 -25
  21. data/lib/discordrb/data.rb +521 -219
  22. data/lib/discordrb/errors.rb +6 -7
  23. data/lib/discordrb/events/await.rb +2 -0
  24. data/lib/discordrb/events/bans.rb +3 -1
  25. data/lib/discordrb/events/channels.rb +124 -0
  26. data/lib/discordrb/events/generic.rb +2 -0
  27. data/lib/discordrb/events/guilds.rb +16 -13
  28. data/lib/discordrb/events/lifetime.rb +12 -2
  29. data/lib/discordrb/events/members.rb +26 -15
  30. data/lib/discordrb/events/message.rb +20 -7
  31. data/lib/discordrb/events/presence.rb +18 -2
  32. data/lib/discordrb/events/roles.rb +83 -0
  33. data/lib/discordrb/events/typing.rb +15 -2
  34. data/lib/discordrb/events/voice_state_update.rb +2 -0
  35. data/lib/discordrb/light.rb +8 -0
  36. data/lib/discordrb/light/data.rb +62 -0
  37. data/lib/discordrb/light/integrations.rb +73 -0
  38. data/lib/discordrb/light/light_bot.rb +56 -0
  39. data/lib/discordrb/logger.rb +4 -0
  40. data/lib/discordrb/permissions.rb +16 -12
  41. data/lib/discordrb/token_cache.rb +3 -0
  42. data/lib/discordrb/version.rb +3 -1
  43. data/lib/discordrb/voice/encoder.rb +2 -0
  44. data/lib/discordrb/voice/network.rb +21 -14
  45. data/lib/discordrb/voice/voice_bot.rb +26 -3
  46. data/lib/discordrb/websocket.rb +69 -0
  47. metadata +15 -26
  48. data/lib/discordrb/events/channel_create.rb +0 -44
  49. data/lib/discordrb/events/channel_delete.rb +0 -44
  50. data/lib/discordrb/events/channel_update.rb +0 -46
  51. data/lib/discordrb/events/guild_role_create.rb +0 -35
  52. data/lib/discordrb/events/guild_role_delete.rb +0 -36
  53. data/lib/discordrb/events/guild_role_update.rb +0 -35
@@ -1,15 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'discordrb/events/message'
2
4
  require 'discordrb/events/typing'
3
5
  require 'discordrb/events/lifetime'
4
6
  require 'discordrb/events/presence'
5
7
  require 'discordrb/events/voice_state_update'
6
- require 'discordrb/events/channel_create'
7
- require 'discordrb/events/channel_update'
8
- require 'discordrb/events/channel_delete'
8
+ require 'discordrb/events/channels'
9
9
  require 'discordrb/events/members'
10
- require 'discordrb/events/guild_role_create'
11
- require 'discordrb/events/guild_role_delete'
12
- require 'discordrb/events/guild_role_update'
10
+ require 'discordrb/events/roles'
13
11
  require 'discordrb/events/guilds'
14
12
  require 'discordrb/events/await'
15
13
  require 'discordrb/events/bans'
@@ -189,30 +187,30 @@ module Discordrb
189
187
  # @param attributes [Hash] The event's attributes.
190
188
  # @option attributes [String] :username Matches the username of the joined user.
191
189
  # @yield The block is executed when the event is raised.
192
- # @yieldparam event [GuildMemberAddEvent] The event that was raised.
193
- # @return [GuildMemberAddEventHandler] The event handler that was registered.
190
+ # @yieldparam event [ServerMemberAddEvent] The event that was raised.
191
+ # @return [ServerMemberAddEventHandler] The event handler that was registered.
194
192
  def member_join(attributes = {}, &block)
195
- register_event(GuildMemberAddEvent, attributes, block)
193
+ register_event(ServerMemberAddEvent, attributes, block)
196
194
  end
197
195
 
198
196
  # This **event** is raised when a member update happens.
199
197
  # @param attributes [Hash] The event's attributes.
200
198
  # @option attributes [String] :username Matches the username of the updated user.
201
199
  # @yield The block is executed when the event is raised.
202
- # @yieldparam event [GuildMemberUpdateEvent] The event that was raised.
203
- # @return [GuildMemberUpdateEventHandler] The event handler that was registered.
200
+ # @yieldparam event [ServerMemberUpdateEvent] The event that was raised.
201
+ # @return [ServerMemberUpdateEventHandler] The event handler that was registered.
204
202
  def member_update(attributes = {}, &block)
205
- register_event(GuildMemberUpdateEvent, attributes, block)
203
+ register_event(ServerMemberUpdateEvent, attributes, block)
206
204
  end
207
205
 
208
206
  # This **event** is raised when a member leaves a server.
209
207
  # @param attributes [Hash] The event's attributes.
210
208
  # @option attributes [String] :username Matches the username of the member.
211
209
  # @yield The block is executed when the event is raised.
212
- # @yieldparam event [GuildMemberDeleteEvent] The event that was raised.
213
- # @return [GuildMemberDeleteEventHandler] The event handler that was registered.
210
+ # @yieldparam event [ServerMemberDeleteEvent] The event that was raised.
211
+ # @return [ServerMemberDeleteEventHandler] The event handler that was registered.
214
212
  def member_leave(attributes = {}, &block)
215
- register_event(GuildMemberDeleteEvent, attributes, block)
213
+ register_event(ServerMemberDeleteEvent, attributes, block)
216
214
  end
217
215
 
218
216
  # This **event** is raised when a user is banned from a server.
@@ -243,20 +241,20 @@ module Discordrb
243
241
  # @param attributes [Hash] The event's attributes.
244
242
  # @option attributes [String, Integer, Server] :server Matches the server that was created.
245
243
  # @yield The block is executed when the event is raised.
246
- # @yieldparam event [GuildCreateEvent] The event that was raised.
247
- # @return [GuildCreateEventHandler] The event handler that was registered.
244
+ # @yieldparam event [ServerCreateEvent] The event that was raised.
245
+ # @return [ServerCreateEventHandler] The event handler that was registered.
248
246
  def server_create(attributes = {}, &block)
249
- register_event(GuildCreateEvent, attributes, block)
247
+ register_event(ServerCreateEvent, attributes, block)
250
248
  end
251
249
 
252
250
  # This **event** is raised when a server is updated, for example if the name or region has changed.
253
251
  # @param attributes [Hash] The event's attributes.
254
252
  # @option attributes [String, Integer, Server] :server Matches the server that was updated.
255
253
  # @yield The block is executed when the event is raised.
256
- # @yieldparam event [GuildUpdateEvent] The event that was raised.
257
- # @return [GuildUpdateEventHandler] The event handler that was registered.
254
+ # @yieldparam event [ServerUpdateEvent] The event that was raised.
255
+ # @return [ServerUpdateEventHandler] The event handler that was registered.
258
256
  def server_update(attributes = {}, &block)
259
- register_event(GuildUpdateEvent, attributes, block)
257
+ register_event(ServerUpdateEvent, attributes, block)
260
258
  end
261
259
 
262
260
  # This **event** is raised when a server is deleted, or when the bot leaves a server. (These two cases are identical
@@ -264,10 +262,10 @@ module Discordrb
264
262
  # @param attributes [Hash] The event's attributes.
265
263
  # @option attributes [String, Integer, Server] :server Matches the server that was deleted.
266
264
  # @yield The block is executed when the event is raised.
267
- # @yieldparam event [GuildDeleteEvent] The event that was raised.
268
- # @return [GuildDeleteEventHandler] The event handler that was registered.
265
+ # @yieldparam event [ServerDeleteEvent] The event that was raised.
266
+ # @return [ServerDeleteEventHandler] The event handler that was registered.
269
267
  def server_delete(attributes = {}, &block)
270
- register_event(GuildDeleteEvent, attributes, block)
268
+ register_event(ServerDeleteEvent, attributes, block)
271
269
  end
272
270
 
273
271
  # This **event** is raised when an {Await} is triggered. It provides an easy way to execute code
@@ -330,7 +328,8 @@ module Discordrb
330
328
  # @param container [Module] A module that `extend`s {EventContainer} from which the handlers will be added.
331
329
  def include_events(container)
332
330
  handlers = container.instance_variable_get '@event_handlers'
333
- raise "Couldn't include the container #{container} as it doesn't have any event handlers - have you tried to include a commands container into an event-only bot?" unless handlers
331
+ return unless handlers
332
+
334
333
  @event_handlers ||= {}
335
334
  @event_handlers.merge!(handlers) { |_, old, new| old + new }
336
335
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # These classes hold relevant Discord data, such as messages or channels.
2
4
 
3
5
  require 'ostruct'
@@ -10,13 +12,55 @@ require 'base64'
10
12
  # Discordrb module
11
13
  module Discordrb
12
14
  # The unix timestamp Discord IDs are based on
13
- DISCORD_EPOCH = 1420070400000
15
+ DISCORD_EPOCH = 1_420_070_400_000
14
16
 
15
17
  # Compares two objects based on IDs - either the objects' IDs are equal, or one object is equal to the other's ID.
16
18
  def self.id_compare(one_id, other)
17
19
  other.respond_to?(:resolve_id) ? (one_id.resolve_id == other.resolve_id) : (one_id == other)
18
20
  end
19
21
 
22
+ # The maximum length a Discord message can have
23
+ CHARACTER_LIMIT = 2000
24
+
25
+ # Splits a message into chunks of 2000 characters. Attempts to split by lines if possible.
26
+ # @param msg [String] The message to split.
27
+ # @return [Array<String>] the message split into chunks
28
+ def self.split_message(msg)
29
+ # If the messages is empty, return an empty array
30
+ return [] if msg.empty?
31
+
32
+ # Split the message into lines
33
+ lines = msg.lines
34
+
35
+ # Turn the message into a "triangle" of consecutively longer slices, for example the array [1,2,3,4] would become
36
+ # [
37
+ # [1],
38
+ # [1, 2],
39
+ # [1, 2, 3],
40
+ # [1, 2, 3, 4]
41
+ # ]
42
+ tri = [*0..(lines.length - 1)].map { |i| lines.combination(i + 1).first }
43
+
44
+ # Join the individual elements together to get an array of strings with consecutively more lines
45
+ joined = tri.map { |e| e.join("\n") }
46
+
47
+ # Find the largest element that is still below the character limit, or if none such element exists return the first
48
+ ideal = joined.max_by { |e| e.length > CHARACTER_LIMIT ? -1 : e.length }
49
+
50
+ # If it's still larger than the character limit (none was smaller than it) split it into slices with the length
51
+ # 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]
53
+
54
+ # Slice off the ideal part and strip newlines
55
+ rest = msg[ideal.length..-1].strip
56
+
57
+ # If none remains, return an empty array -> we're done
58
+ return [] unless rest
59
+
60
+ # Otherwise, call the method recursively to split the rest of the string and add it onto the ideal array
61
+ ideal_ary + split_message(rest)
62
+ end
63
+
20
64
  # Mixin for objects that have IDs
21
65
  module IDObject
22
66
  # @return [Integer] the ID which uniquely identifies this object across Discord.
@@ -38,27 +82,49 @@ module Discordrb
38
82
  end
39
83
  end
40
84
 
41
- # User on Discord, including internal data like discriminators
42
- class User
43
- include IDObject
44
-
85
+ # Mixin for the attributes users should have
86
+ module UserAttributes
45
87
  # @return [String] this user's username
46
88
  attr_reader :username
89
+ alias_method :name, :username
47
90
 
48
91
  # @return [String] this user's discriminator which is used internally to identify users with identical usernames.
49
92
  attr_reader :discriminator
93
+ alias_method :discrim, :discriminator
50
94
  alias_method :tag, :discriminator
51
95
  alias_method :discord_tag, :discriminator
52
96
 
97
+ # @return [true, false] whether this user is a Discord bot account
98
+ attr_reader :bot_account
99
+ alias_method :bot_account?, :bot_account
100
+
53
101
  # @return [String] the ID of this user's current avatar, can be used to generate an avatar URL.
54
102
  # @see #avatar_url
55
103
  attr_reader :avatar_id
56
104
 
57
- # @return [Channel, nil] the voice channel this user is on currently.
58
- attr_reader :voice_channel
105
+ # Utility function to mention users in messages
106
+ # @return [String] the mention code in the form of <@id>
107
+ def mention
108
+ "<@#{@id}>"
109
+ end
59
110
 
60
- # @return [Hash<Integer => Array<Role>>] the roles this user has, grouped by server ID.
61
- attr_reader :roles
111
+ # Utility function to get Discord's distinct representation of a user, i. e. username + discriminator
112
+ # @return [String] distinct representation of user
113
+ def distinct
114
+ "#{@username}##{@discriminator}"
115
+ end
116
+
117
+ # Utility function to get a user's avatar URL.
118
+ # @return [String] the URL to the avatar image.
119
+ def avatar_url
120
+ API.avatar_url(@id, @avatar_id)
121
+ end
122
+ end
123
+
124
+ # User on Discord, including internal data like discriminators
125
+ class User
126
+ include IDObject
127
+ include UserAttributes
62
128
 
63
129
  # @!attribute [r] status
64
130
  # @return [Symbol] the current online status of the user (`:online`, `:offline` or `:idle`)
@@ -68,16 +134,6 @@ module Discordrb
68
134
  # @return [String, nil] the game the user is currently playing, or `nil` if none is being played.
69
135
  attr_accessor :game
70
136
 
71
- # @!attribute [r] self_mute
72
- # @return [true, false] whether or not the user is currently muted by the bot.
73
- attr_accessor :self_mute
74
-
75
- # @todo Fix these (server_mute and _deaf should be server specific, not sure about self_deaf or what it does anyway)
76
- # @!visibility private
77
- attr_accessor :server_mute, :server_deaf, :self_deaf
78
-
79
- alias_method :name, :username
80
-
81
137
  def initialize(data, bot)
82
138
  @bot = bot
83
139
 
@@ -87,26 +143,10 @@ module Discordrb
87
143
  @avatar_id = data['avatar']
88
144
  @roles = {}
89
145
 
90
- @status = :offline
91
- end
92
-
93
- # Gets the user's avatar ID.
94
- # @deprecated Use {#avatar_id} instead.
95
- def avatar
96
- LOGGER.debug('Warning: Deprecated reader User.avatar was used! Use User.avatar_id (or User.avatar_url if you just want the URL) instead.', true)
97
- @avatar_id
98
- end
99
-
100
- # Utility function to mention users in messages
101
- # @return [String] the mention code in the form of <@id>
102
- def mention
103
- "<@#{@id}>"
104
- end
146
+ @bot_account = false
147
+ @bot_account = true if data['bot']
105
148
 
106
- # Utility function to get a user's avatar URL.
107
- # @return [String] the URL to the avatar image.
108
- def avatar_url
109
- API.avatar_url(@id, @avatar_id)
149
+ @status = :offline
110
150
  end
111
151
 
112
152
  # Get a user's PM channel or send them a PM
@@ -128,44 +168,6 @@ module Discordrb
128
168
  end
129
169
  end
130
170
 
131
- # Changes a user's voice channel.
132
- # @note For internal use only
133
- # @!visibility private
134
- def move(to_channel)
135
- return if to_channel && to_channel.type != 'voice'
136
- @voice_channel = to_channel
137
- end
138
-
139
- # Adds a role to this user on the specified server.
140
- # @param server [Server] The server on which to add the role.
141
- # @param role [Role] The role to add.
142
- def add_role(server, role)
143
- user_roles = @roles[server.id] || []
144
- user_roles << role
145
- ids = user_roles.map(&:id)
146
- API.update_user_roles(@bot.token, server.id, @id, ids)
147
- end
148
-
149
- # Removes a role from this user on the specified server.
150
- # @param server [Server] The server on which to remove the role.
151
- # @param role [Role] The role to remove.
152
- def remove_role(server, role)
153
- user_roles = @roles[server.id] || []
154
-
155
- # If the given role has an ID (i.e. is a Role object), then check whether its ID is equal, otherwise check whether it's equal directly
156
- user_roles.delete_if { |e| e == role }
157
- ids = user_roles.map(&:id)
158
- API.update_user_roles(@bot.token, server.id, @id, ids)
159
- end
160
-
161
- # Set this user's roles in the cache
162
- # @note For internal use only
163
- # @!visibility private
164
- def update_roles(server, roles)
165
- @roles ||= {}
166
- @roles[server.id] = roles
167
- end
168
-
169
171
  # Set the user's name
170
172
  # @note for internal use only
171
173
  # @!visibility private
@@ -173,24 +175,6 @@ module Discordrb
173
175
  @username = username
174
176
  end
175
177
 
176
- # Merge this user's roles with the roles from another instance of this user (from another server)
177
- # @note For internal use only
178
- # @!visibility private
179
- def merge_roles(server, roles)
180
- @roles[server.id] = if @roles[server.id]
181
- (@roles[server.id] + roles).uniq
182
- else
183
- roles
184
- end
185
- end
186
-
187
- # Delete a specific server from the roles (in case a user leaves a server)
188
- # @note For internal use only
189
- # @!visibility private
190
- def delete_roles(server_id)
191
- @roles.delete(server_id)
192
- end
193
-
194
178
  # Add an await for a message from this user. Specifically, this adds a global await for a MessageEvent with this
195
179
  # user's ID as a :from attribute.
196
180
  # @see Bot#add_await
@@ -198,54 +182,218 @@ module Discordrb
198
182
  @bot.add_await(key, Discordrb::Events::MessageEvent, { from: @id }.merge(attributes), &block)
199
183
  end
200
184
 
185
+ # Gets the member this user is on a server
186
+ # @param server [Server] The server to get the member for
187
+ # @return [Member] this user as a member on a particular server
188
+ def on(server)
189
+ id = server.resolve_id
190
+ @bot.server(id).member(@id)
191
+ end
192
+
201
193
  # Is the user the bot?
202
194
  # @return [true, false] whether this user is the bot
203
- def bot?
204
- @bot.bot_user.id == @id
195
+ def current_bot?
196
+ @bot.profile.id == @id
205
197
  end
206
198
 
207
- # Determines whether this user has a specific permission on a server (and channel).
199
+ # The inspect method is overwritten to give more useful output
200
+ def inspect
201
+ "<User username=#{@username} id=#{@id} discriminator=#{@discriminator}>"
202
+ end
203
+ end
204
+
205
+ # Mixin for the attributes members and private members should have
206
+ module MemberAttributes
207
+ # @return [true, false] whether this member is muted server-wide.
208
+ attr_reader :mute
209
+ alias_method :muted?, :mute
210
+
211
+ # @return [true, false] whether this member is deafened server-wide.
212
+ attr_reader :deaf
213
+ alias_method :deafened?, :deaf
214
+
215
+ # @return [Time] when this member joined the server.
216
+ attr_reader :joined_at
217
+
218
+ # @return [Array<Role>] the roles this member has.
219
+ attr_reader :roles
220
+
221
+ # @return [Server] the server this member is on.
222
+ attr_reader :server
223
+
224
+ # @return [Channel] the voice channel the user is in.
225
+ attr_reader :voice_channel
226
+ end
227
+
228
+ # Mixin to calculate resulting permissions from overrides etc.
229
+ module PermissionCalculator
230
+ # Checks whether this user can do the particular action, regardless of whether it has the permission defined,
231
+ # through for example being the server owner or having the Manage Roles permission
208
232
  # @param action [Symbol] The permission that should be checked. See also {Permissions::Flags} for a list.
209
- # @param server [Server] The server on which the permission should be checked.
210
233
  # @param channel [Channel, nil] If channel overrides should be checked too, this channel specifies where the overrides should be checked.
211
234
  # @return [true, false] whether or not this user has the permission.
212
- def permission?(action, server, channel = nil)
235
+ def permission?(action, channel = nil)
236
+ # If the member is the server owner, it irrevocably has all permissions.
237
+ return true if owner?
238
+
239
+ # First, check whether the user has Manage Roles defined.
240
+ # (Coincidentally, Manage Permissions is the same permission as Manage Roles, and a
241
+ # Manage Permissions deny overwrite will override Manage Roles, so we can just check for
242
+ # Manage Roles once and call it a day.)
243
+ return true if defined_permission?(:manage_roles, channel)
244
+
245
+ # Otherwise, defer to defined_permission
246
+ defined_permission?(action, channel)
247
+ end
248
+
249
+ # Checks whether this user has a particular permission defined (i. e. not implicit, through for example
250
+ # Manage Roles)
251
+ # @param action [Symbol] The permission that should be checked. See also {Permissions::Flags} for a list.
252
+ # @param channel [Channel, nil] If channel overrides should be checked too, this channel specifies where the overrides should be checked.
253
+ # @return [true, false] whether or not this user has the permission defined.
254
+ def defined_permission?(action, channel = nil)
255
+ # Get the permission the user's roles have
256
+ role_permission = defined_role_permission?(action, channel)
257
+
258
+ # Once we have checked the role permission, we have to check the channel overrides for the
259
+ # specific user
260
+ user_specific_override = permission_overwrite(action, channel, id) # Use the ID reader as members have no ID instance variable
261
+
262
+ # Merge the two permissions - if an override is defined, it has to be allow, otherwise we only care about the role
263
+ return role_permission unless user_specific_override
264
+ user_specific_override == :allow
265
+ end
266
+
267
+ # Define methods for querying permissions
268
+ Discordrb::Permissions::Flags.each_value do |flag|
269
+ define_method "can_#{flag}?" do |channel = nil|
270
+ permission? flag, channel
271
+ end
272
+ end
273
+
274
+ private
275
+
276
+ def defined_role_permission?(action, channel)
213
277
  # For each role, check if
214
278
  # (1) the channel explicitly allows or permits an action for the role and
215
279
  # (2) if the user is allowed to do the action if the channel doesn't specify
216
- return false unless @roles[server.id]
217
-
218
- @roles[server.id].reduce(false) do |can_act, role|
219
- channel_allow = nil
220
- if channel && channel.permission_overwrites[role.id]
221
- allow = channel.permission_overwrites[role.id].allow
222
- deny = channel.permission_overwrites[role.id].deny
223
- if allow.instance_variable_get("@#{action}")
224
- channel_allow = true
225
- elsif deny.instance_variable_get("@#{action}")
226
- channel_allow = false
227
- end
228
- # If the channel has nothing to say on the matter, we can defer to the role itself
229
- end
230
- can_act = if channel_allow.nil?
231
- role.permissions.instance_variable_get("@#{action}") || can_act
280
+ @roles.reduce(false) do |can_act, role|
281
+ # Get the override defined for the role on the channel
282
+ channel_allow = permission_overwrite(action, channel, role.id)
283
+ can_act = if channel_allow
284
+ # If the channel has an override, check whether it is an allow - if yes,
285
+ # the user can act, if not, it can't
286
+ channel_allow == :allow
232
287
  else
233
- channel_allow
288
+ # Otherwise defer to the role
289
+ role.permissions.instance_variable_get("@#{action}") || can_act
234
290
  end
235
291
  can_act
236
292
  end
237
293
  end
238
294
 
239
- # Define methods for querying permissions
240
- Discordrb::Permissions::Flags.each_value do |flag|
241
- define_method "can_#{flag}?" do |server, channel = nil|
242
- permission? flag, server, channel
295
+ def permission_overwrite(action, channel, id)
296
+ # If no overwrites are defined, or no channel is set, no overwrite will be present
297
+ return nil unless channel && channel.permission_overwrites[id]
298
+
299
+ # Otherwise, check the allow and deny objects
300
+ allow = channel.permission_overwrites[id].allow
301
+ deny = channel.permission_overwrites[id].deny
302
+ if allow.instance_variable_get("@#{action}")
303
+ :allow
304
+ elsif deny.instance_variable_get("@#{action}")
305
+ :deny
243
306
  end
307
+
308
+ # If there's no variable defined, nil will implicitly be returned
244
309
  end
310
+ end
245
311
 
246
- # The inspect method is overwritten to give more useful output
312
+ # A member is a user on a server. It differs from regular users in that it has roles, voice statuses and things like
313
+ # that.
314
+ class Member < DelegateClass(User)
315
+ include MemberAttributes
316
+
317
+ # @!visibility private
318
+ def initialize(data, server, bot)
319
+ @bot = bot
320
+
321
+ @user = bot.ensure_user(data['user'])
322
+ super @user # Initialize the delegate class
323
+
324
+ # Somehow, Discord doesn't send the server ID in the standard member format...
325
+ raise ArgumentError, 'Cannot create a member without any information about the server!' if server.nil? && data['guild_id'].nil?
326
+ @server = server || bot.server(data['guild_id'].to_i)
327
+
328
+ # Initialize the roles by getting the roles from the server one-by-one
329
+ update_roles(data['roles'])
330
+
331
+ @deaf = data['deaf']
332
+ @mute = data['mute']
333
+ @joined_at = data['joined_at'] ? Time.parse(data['joined_at']) : nil
334
+ end
335
+
336
+ # @return [true, false] whether this member is the server owner.
337
+ def owner?
338
+ @server.owner == self
339
+ end
340
+
341
+ # Update this member's roles
342
+ # @note For internal use only.
343
+ # @!visibility private
344
+ def update_roles(roles)
345
+ @roles = roles.map do |role_id|
346
+ @server.role(role_id.to_i)
347
+ end
348
+ end
349
+
350
+ # Update this member's voice state
351
+ # @note For internal use only.
352
+ # @!visibility private
353
+ def update_voice_state(channel, mute, deaf, self_mute, self_deaf)
354
+ @voice_channel = channel
355
+ @mute = mute
356
+ @deaf = deaf
357
+ @self_mute = self_mute
358
+ @self_deaf = self_deaf
359
+ end
360
+
361
+ include PermissionCalculator
362
+
363
+ # Overwriting inspect for debug purposes
247
364
  def inspect
248
- "<User username=#{@username} id=#{@id} discriminator=#{@discriminator}>"
365
+ "<Member user=#{@user.inspect} server=#{@server.inspect} joined_at=#{@joined_at} roles=#{@roles.inspect} voice_channel=#{@voice_channel.inspect} mute=#{@mute} deaf=#{@deaf} self_mute=#{@self_mute} self_deaf=#{@self_deaf}>"
366
+ end
367
+ end
368
+
369
+ # Recipients are members on private channels - they exist for completeness purposes, but all
370
+ # the attributes will be empty.
371
+ class Recipient < DelegateClass(User)
372
+ include MemberAttributes
373
+
374
+ # @return [Channel] the private channel this recipient is the recipient of.
375
+ attr_reader :channel
376
+
377
+ # @!visibility private
378
+ def initialize(user, channel, bot)
379
+ @bot = bot
380
+ @channel = channel
381
+ raise ArgumentError, 'Tried to create a recipient for a public channel!' unless @channel.private?
382
+
383
+ @user = user
384
+ super @user
385
+
386
+ # Member attributes
387
+ @mute = @deaf = @self_mute = @self_deaf = false
388
+ @voice_channel = nil
389
+ @server = nil
390
+ @roles = []
391
+ @joined_at = @channel.creation_time
392
+ end
393
+
394
+ # Overwriting inspect for debug purposes
395
+ def inspect
396
+ "<Recipient user=#{@user.inspect} channel=#{@channel.inspect}>"
249
397
  end
250
398
  end
251
399
 
@@ -260,38 +408,39 @@ module Discordrb
260
408
 
261
409
  # Whether or not the user is the bot. The Profile can only ever be the bot user, so this always returns true.
262
410
  # @return [true]
263
- def bot?
411
+ def current_bot?
264
412
  true
265
413
  end
266
414
 
267
415
  # Sets the bot's username.
268
416
  # @param username [String] The new username.
269
417
  def username=(username)
270
- update_server_data(username: username)
418
+ update_profile_data(username: username)
271
419
  end
272
420
 
273
421
  # Sets the bot's email address. If you use this method, make sure that the login email in the script matches this
274
422
  # one afterwards, so the bot doesn't have any trouble logging in in the future.
275
423
  # @param email [String] The new email address.
276
424
  def email=(email)
277
- update_server_data(email: email)
425
+ update_profile_data(email: email)
278
426
  end
279
427
 
280
428
  # Changes the bot's password. This will invalidate all tokens so you will have to relog the bot.
281
429
  # @param password [String] The new password.
282
430
  def password=(password)
283
- update_server_data(new_password: password)
431
+ update_profile_data(new_password: password)
284
432
  end
285
433
 
286
434
  # Changes the bot's avatar.
287
- # @param avatar [String, File] A JPG file to be used as the avatar, either as a File object or as a base64-encoded String.
435
+ # @param avatar [String, #read] A JPG file to be used as the avatar, either
436
+ # something readable (e. g. File) or as a data URL.
288
437
  def avatar=(avatar)
289
438
  if avatar.respond_to? :read
290
439
  avatar_string = 'data:image/jpg;base64,'
291
440
  avatar_string += Base64.strict_encode64(avatar.read)
292
- update_server_data(avatar: avatar_string)
441
+ update_profile_data(avatar: avatar_string)
293
442
  else
294
- update_server_data(avatar: avatar)
443
+ update_profile_data(avatar: avatar)
295
444
  end
296
445
  end
297
446
 
@@ -312,7 +461,7 @@ module Discordrb
312
461
 
313
462
  private
314
463
 
315
- def update_server_data(new_data)
464
+ def update_profile_data(new_data)
316
465
  API.update_user(@bot.token,
317
466
  new_data[:email] || @email,
318
467
  @password,
@@ -525,14 +674,7 @@ module Discordrb
525
674
  # @return [String] the type of this channel (currently either 'text' or 'voice')
526
675
  attr_reader :type
527
676
 
528
- # @note This data is sent by Discord and it's possible for this to falsely be true for certain kinds of integration
529
- # channels (like Twitch subscriber ones). This appears to be a Discord bug that I can't reproduce myself, due to
530
- # not having any integrations in place. If this occurs to you please tell me.
531
- # @deprecated Use {#private?} instead, it's guaranteed to be accurate.
532
- # @return [true, false] whether or not this channel is a private messaging channel.
533
- attr_reader :is_private
534
-
535
- # @return [User, nil] the recipient of the private messages, or nil if this is not a PM channel
677
+ # @return [Recipient, nil] the recipient of the private messages, or nil if this is not a PM channel
536
678
  attr_reader :recipient
537
679
 
538
680
  # @return [String] the channel's topic
@@ -546,7 +688,7 @@ module Discordrb
546
688
  # @return [Hash<Integer => OpenStruct>] the channel's permission overwrites
547
689
  attr_reader :permission_overwrites
548
690
 
549
- # @return [true, false] whether or not this channel is a PM channel, with more accuracy than {#is_private}.
691
+ # @return [true, false] whether or not this channel is a PM channel.
550
692
  def private?
551
693
  @server.nil?
552
694
  end
@@ -570,12 +712,16 @@ module Discordrb
570
712
 
571
713
  @is_private = data['is_private']
572
714
  if @is_private
573
- @recipient = User.new(data['recipient'], bot)
715
+ recipient_user = bot.ensure_user(data['recipient'])
716
+ @recipient = Recipient.new(recipient_user, self, bot)
574
717
  @name = @recipient.username
575
718
  else
576
719
  @name = data['name']
577
- @server = bot.server(data['guild_id'].to_i)
578
- @server ||= server
720
+ @server = if server
721
+ server
722
+ else
723
+ bot.server(data['guild_id'].to_i)
724
+ end
579
725
  end
580
726
 
581
727
  # Populate permission overwrites
@@ -609,6 +755,18 @@ module Discordrb
609
755
  @bot.send_message(@id, content, tts)
610
756
  end
611
757
 
758
+ # Sends multiple messages to a channel
759
+ # @param content [Array<String>] The messages to send.
760
+ def send_multiple(content)
761
+ content.each { |e| send_message(e) }
762
+ end
763
+
764
+ # Splits a message into chunks whose length is at most the Discord character limit, then sends them individually.
765
+ # Useful for sending long messages, but be wary of rate limits!
766
+ def split_send(content)
767
+ send_multiple(Discordrb.split_message(content))
768
+ end
769
+
612
770
  # Sends a file to this channel. If it is an image, it will be embedded.
613
771
  # @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)
614
772
  def send_file(file)
@@ -641,20 +799,46 @@ module Discordrb
641
799
  update_channel_data
642
800
  end
643
801
 
802
+ # Defines a permission overwrite for this channel that sets the specified thing to the specified allow and deny
803
+ # permission sets, or change an existing one.
804
+ # @param thing [User, Role] What to define an overwrite for.
805
+ # @param allow [#bits, Permissions, Integer] The permission sets that should receive an `allow` override (i. e. a
806
+ # green checkmark on Discord)
807
+ # @param deny [#bits, Permissions, Integer] The permission sets that should receive a `deny` override (i. e. a red
808
+ # cross on Discord)
809
+ # @example Define a permission overwrite for a user that can then mention everyone and use TTS, but not create any invites
810
+ # allow = Discordrb::Permissions.new
811
+ # allow.can_mention_everyone = true
812
+ # allow.can_send_tts_messages = true
813
+ #
814
+ # deny = Discordrb::Permissions.new
815
+ # deny.can_create_instant_invite = true
816
+ #
817
+ # channel.define_overwrite(user, allow, deny)
818
+ def define_overwrite(thing, allow, deny)
819
+ allow_bits = allow.respond_to?(:bits) ? allow.bits : allow
820
+ deny_bits = deny.respond_to?(:bits) ? deny.bits : deny
821
+
822
+ if thing.is_a? User
823
+ API.update_user_overrides(@bot.token, @id, thing.id, allow_bits, deny_bits)
824
+ elsif thing.is_a? Role
825
+ API.update_role_overrides(@bot.token, @id, thing.id, allow_bits, deny_bits)
826
+ end
827
+ end
828
+
644
829
  # Updates the cached data from another channel.
645
830
  # @note For internal use only
646
831
  # @!visibility private
647
832
  def update_from(other)
648
833
  @topic = other.topic
649
834
  @name = other.name
650
- @is_private = other.is_private
651
835
  @recipient = other.recipient
652
836
  @permission_overwrites = other.permission_overwrites
653
837
  end
654
838
 
655
839
  # The list of users currently in this channel. This is mostly useful for a voice channel, for a text channel it will
656
840
  # just return the users on the server that are online.
657
- # @return [Array<User>] the users in this channel
841
+ # @return [Array<Member>] the users in this channel
658
842
  def users
659
843
  if @type == 'text'
660
844
  @server.members.select { |u| u.status != :offline }
@@ -746,6 +930,53 @@ module Discordrb
746
930
  end
747
931
  end
748
932
 
933
+ # An attachment to a message
934
+ class Attachment
935
+ include IDObject
936
+
937
+ # @return [Message] the message this attachment belongs to.
938
+ attr_reader :message
939
+
940
+ # @return [String] the CDN URL this attachment can be downloaded at.
941
+ attr_reader :url
942
+
943
+ # @return [String] the attachment's proxy URL - I'm not sure what exactly this does, but I think it has something to
944
+ # do with CDNs
945
+ attr_reader :proxy_url
946
+
947
+ # @return [String] the attachment's filename.
948
+ attr_reader :filename
949
+
950
+ # @return [Integer] the attachment's file size in bytes.
951
+ attr_reader :size
952
+
953
+ # @return [Integer, nil] the width of an image file, in pixels, or nil if the file is not an image.
954
+ attr_reader :width
955
+
956
+ # @return [Integer, nil] the height of an image file, in pixels, or nil if the file is not an image.
957
+ attr_reader :height
958
+
959
+ # @!visibility private
960
+ def initialize(data, message, bot)
961
+ @bot = bot
962
+ @message = message
963
+
964
+ @url = data['url']
965
+ @proxy_url = data['proxy_url']
966
+ @filename = data['filename']
967
+
968
+ @size = data['size']
969
+
970
+ @width = data['width']
971
+ @height = data['height']
972
+ end
973
+
974
+ # @return [true, false] whether this file is an image file.
975
+ def image?
976
+ !(@width.nil? || @height.nil?)
977
+ end
978
+ end
979
+
749
980
  # A message on Discord that was sent to a text channel
750
981
  class Message
751
982
  include IDObject
@@ -753,7 +984,7 @@ module Discordrb
753
984
  # @return [String] the content of this message.
754
985
  attr_reader :content
755
986
 
756
- # @return [User] the user that sent this message.
987
+ # @return [Member] the user that sent this message.
757
988
  attr_reader :author
758
989
 
759
990
  # @return [Channel] the channel in which this message was sent.
@@ -765,6 +996,9 @@ module Discordrb
765
996
  # @return [Array<User>] the users that were mentioned in this message.
766
997
  attr_reader :mentions
767
998
 
999
+ # @return [Array<Attachment>] the files attached to this message.
1000
+ attr_reader :attachments
1001
+
768
1002
  alias_method :user, :author
769
1003
  alias_method :text, :content
770
1004
  alias_method :to_s, :content
@@ -773,16 +1007,29 @@ module Discordrb
773
1007
  def initialize(data, bot)
774
1008
  @bot = bot
775
1009
  @content = data['content']
776
- @author = bot.user(data['author']['id'].to_i)
777
1010
  @channel = bot.channel(data['channel_id'].to_i)
778
- @timestamp = Time.parse(data['timestamp'])
1011
+
1012
+ @author = if data['author']
1013
+ if @channel.private?
1014
+ # Turn the message user into a recipient - we can't use the channel recipient
1015
+ # directly because the bot may also send messages to the channel
1016
+ Recipient.new(bot.user(data['author']['id'].to_i), @channel, bot)
1017
+ else
1018
+ @channel.server.member(data['author']['id'].to_i)
1019
+ end
1020
+ end
1021
+
1022
+ @timestamp = Time.parse(data['timestamp']) if data['timestamp']
779
1023
  @id = data['id'].to_i
780
1024
 
781
1025
  @mentions = []
782
1026
 
783
1027
  data['mentions'].each do |element|
784
- @mentions << User.new(element, bot)
785
- end
1028
+ @mentions << bot.ensure_user(element)
1029
+ end if data['mentions']
1030
+
1031
+ @attachments = []
1032
+ @attachments = data['attachments'].map { |e| Attachment.new(e, self, @bot) } if data['attachments']
786
1033
  end
787
1034
 
788
1035
  # Replies to this message with the specified content.
@@ -793,13 +1040,16 @@ module Discordrb
793
1040
 
794
1041
  # Edits this message to have the specified content instead.
795
1042
  # @param new_content [String] the new content the message should have.
1043
+ # @return [Message] the resulting message.
796
1044
  def edit(new_content)
797
- API.edit_message(@bot.token, @channel.id, @id, new_content)
1045
+ response = API.edit_message(@bot.token, @channel.id, @id, new_content)
1046
+ Message.new(JSON.parse(response), @bot)
798
1047
  end
799
1048
 
800
1049
  # Deletes this message.
801
1050
  def delete
802
1051
  API.delete_message(@bot.token, @channel.id, @id)
1052
+ nil
803
1053
  end
804
1054
 
805
1055
  # Add an {Await} for a message with the same user and channel.
@@ -810,7 +1060,7 @@ module Discordrb
810
1060
 
811
1061
  # @return [true, false] whether this message was sent by the current {Bot}.
812
1062
  def from_bot?
813
- @author.bot?
1063
+ @author && @author.current_bot?
814
1064
  end
815
1065
 
816
1066
  # The inspect method is overwritten to give more useful output
@@ -819,27 +1069,32 @@ module Discordrb
819
1069
  end
820
1070
  end
821
1071
 
1072
+ # Basic attributes a server should have
1073
+ module ServerAttributes
1074
+ # @return [String] this server's name.
1075
+ attr_reader :name
1076
+
1077
+ # @return [String] the hexadecimal ID used to identify this server's icon.
1078
+ attr_reader :icon_id
1079
+
1080
+ # Utility function to get the URL for the icon image
1081
+ # @return [String] the URL to the icon image
1082
+ def icon_url
1083
+ API.icon_url(@id, @icon_id)
1084
+ end
1085
+ end
1086
+
822
1087
  # A server on Discord
823
1088
  class Server
824
1089
  include IDObject
1090
+ include ServerAttributes
825
1091
 
826
1092
  # @return [String] the region the server is on (e. g. `amsterdam`).
827
1093
  attr_reader :region
828
1094
 
829
- # @return [String] this server's name.
830
- attr_reader :name
831
-
832
- # @deprecated Use #owner instead, then get the resulting {User}'s ID.
833
- # @return [Integer] the server owner's user ID.
834
- attr_reader :owner_id
835
-
836
- # @return [User] The server owner.
1095
+ # @return [Member] The server owner.
837
1096
  attr_reader :owner
838
1097
 
839
- # @return [Array<User>] an array of all the users on this server.
840
- attr_reader :members
841
- alias_method :users, :members
842
-
843
1098
  # @return [Array<Channel>] an array of all the channels (text and voice) on this server.
844
1099
  attr_reader :channels
845
1100
 
@@ -854,33 +1109,34 @@ module Discordrb
854
1109
  # @return [Integer] the absolute number of members on this server, offline or not.
855
1110
  attr_reader :member_count
856
1111
 
857
- # @todo Make this behave like user.avatar where a URL is available as well.
858
- # @return [String] the hexadecimal ID used to identify this server's icon.
859
- attr_reader :icon
860
-
861
1112
  # @return [Integer] the amount of time after which a voice user gets moved into the AFK channel, in seconds.
862
1113
  attr_reader :afk_timeout
863
1114
 
864
- # @todo Make this a reader that returns a {Channel}
865
- # @return [Integer] the channel ID of the AFK channel, or `nil` if none is set.
866
- attr_reader :afk_channel_id
1115
+ # @return [Channel, nil] the AFK voice channel of this server, or nil if none is set
1116
+ attr_reader :afk_channel
867
1117
 
868
1118
  # @!visibility private
869
1119
  def initialize(data, bot)
870
1120
  @bot = bot
871
1121
  @owner_id = data['owner_id'].to_i
872
- @owner = bot.user(@owner_id)
873
1122
  @id = data['id'].to_i
874
1123
  update_data(data)
875
1124
 
876
1125
  @large = data['large']
877
1126
  @member_count = data['member_count']
1127
+ @members = {}
878
1128
 
879
1129
  process_roles(data['roles'])
880
1130
  process_members(data['members'])
881
1131
  process_presences(data['presences'])
882
1132
  process_channels(data['channels'])
883
1133
  process_voice_states(data['voice_states'])
1134
+
1135
+ # Whether this server's members have been chunked (resolved using op 8 and GUILD_MEMBERS_CHUNK) yet
1136
+ @chunked = false
1137
+ @processed_chunk_members = 0
1138
+
1139
+ @owner = member(@owner_id)
884
1140
  end
885
1141
 
886
1142
  # @return [Channel] The default channel on this server (usually called #general)
@@ -896,6 +1152,28 @@ module Discordrb
896
1152
  @roles.find { |e| e.id == id }
897
1153
  end
898
1154
 
1155
+ # Gets a member on this server based on user ID
1156
+ # @param id [Integer] The user ID to look for
1157
+ def member(id)
1158
+ id = id.resolve_id
1159
+ return @members[id] if member_cached?(id)
1160
+
1161
+ member = bot.member(@id, id)
1162
+ @members[id] = member
1163
+ end
1164
+
1165
+ # @return [Array<Member>] an array of all the members on this server.
1166
+ def members
1167
+ return @members.values if @chunked
1168
+
1169
+ @bot.debug("Members for server #{@id} not chunked yet - initiating")
1170
+ @bot.request_chunks(@id)
1171
+ sleep 0.05 until @chunked
1172
+ @members.values
1173
+ end
1174
+
1175
+ alias_method :users, :members
1176
+
899
1177
  # Adds a role to the role cache
900
1178
  # @note For internal use only
901
1179
  # @!visibility private
@@ -908,9 +1186,9 @@ module Discordrb
908
1186
  # @!visibility private
909
1187
  def delete_role(role_id)
910
1188
  @roles.reject! { |r| r.id == role_id }
911
- @members.each do |user|
912
- new_roles = user.roles[@id].reject { |r| r.id == role_id }
913
- user.update_roles(self, new_roles)
1189
+ @members.each do |_, member|
1190
+ new_roles = member.roles.reject { |r| r.id == role_id }
1191
+ member.update_roles(new_roles)
914
1192
  end
915
1193
  @channels.each do |channel|
916
1194
  overwrites = channel.permission_overwrites.reject { |id, _| id == role_id }
@@ -918,26 +1196,40 @@ module Discordrb
918
1196
  end
919
1197
  end
920
1198
 
921
- # Adds a user to the user cache.
1199
+ # Adds a member to the member cache.
922
1200
  # @note For internal use only
923
1201
  # @!visibility private
924
- def add_user(user)
925
- @members << user unless @members.include? user
1202
+ def add_member(member)
1203
+ @members[member.id] = member
926
1204
  @member_count += 1
927
1205
  end
928
1206
 
929
- # Removes a user from the user cache.
1207
+ # Removes a member from the member cache.
930
1208
  # @note For internal use only
931
1209
  # @!visibility private
932
- def delete_user(user_id)
933
- @members.reject! { |member| member.id == user_id }.length
1210
+ def delete_member(user_id)
1211
+ @members.delete(user_id)
934
1212
  @member_count -= 1
935
1213
  end
936
1214
 
1215
+ # Checks whether a member is cached
1216
+ # @note For internal use only
1217
+ # @!visibility private
1218
+ def member_cached?(user_id)
1219
+ @members.include?(user_id)
1220
+ end
1221
+
1222
+ # Adds a member to the cache
1223
+ # @note For internal use only
1224
+ # @!visibility private
1225
+ def cache_member(member)
1226
+ @members[member.id] = member
1227
+ end
1228
+
937
1229
  # Creates a channel on this server with the given name.
938
1230
  # @return [Channel] the created channel.
939
- def create_channel(name)
940
- response = API.create_channel(@bot.token, @id, name, 'text')
1231
+ def create_channel(name, type = 'text')
1232
+ response = API.create_channel(@bot.token, @id, name, type)
941
1233
  Channel.new(JSON.parse(response), @bot)
942
1234
  end
943
1235
 
@@ -959,22 +1251,22 @@ module Discordrb
959
1251
  end
960
1252
 
961
1253
  # Bans a user from this server.
962
- # @param user [User] The user to ban.
1254
+ # @param user [User, #resolve_id] The user to ban.
963
1255
  # @param message_days [Integer] How many days worth of messages sent by the user should be deleted.
964
1256
  def ban(user, message_days = 0)
965
- API.ban_user(@bot.token, @id, user.id, message_days)
1257
+ API.ban_user(@bot.token, @id, user.resolve_id, message_days)
966
1258
  end
967
1259
 
968
1260
  # Unbans a previously banned user from this server.
969
- # @param user [User] The user to unban.
1261
+ # @param user [User, #resolve_id] The user to unban.
970
1262
  def unban(user)
971
- API.unban_user(@bot.token, @id, user.id)
1263
+ API.unban_user(@bot.token, @id, user.resolve_id)
972
1264
  end
973
1265
 
974
1266
  # Kicks a user from this server.
975
- # @param user [User] The user to kick.
1267
+ # @param user [User, #resolve_id] The user to kick.
976
1268
  def kick(user)
977
- API.kick_user(@bot.token, @id, user.id)
1269
+ API.kick_user(@bot.token, @id, user.resolve_id)
978
1270
  end
979
1271
 
980
1272
  # Forcibly moves a user into a different voice channel. Only works if the bot has the permission needed.
@@ -1013,10 +1305,15 @@ module Discordrb
1013
1305
  end
1014
1306
 
1015
1307
  # Sets the server's icon.
1016
- # @todo Make this behave in a similar way to User#avatar=.
1017
- # @param icon [String] The new icon, in base64-encoded JPG format.
1308
+ # @param icon [String, #read] The new icon, in base64-encoded JPG format.
1018
1309
  def icon=(icon)
1019
- update_server_data(icon: icon)
1310
+ if icon.respond_to? :read
1311
+ icon_string = 'data:image/jpg;base64,'
1312
+ icon_string += Base64.strict_encode64(icon.read)
1313
+ update_server_data(icon: icon_string)
1314
+ else
1315
+ update_server_data(icon: icon)
1316
+ end
1020
1317
  end
1021
1318
 
1022
1319
  # Sets the server's AFK channel.
@@ -1025,28 +1322,41 @@ module Discordrb
1025
1322
  update_server_data(afk_channel_id: afk_channel.resolve_id)
1026
1323
  end
1027
1324
 
1028
- # @deprecated Use #afk_channel= with the ID instead.
1029
- def afk_channel_id=(afk_channel_id)
1030
- update_server_data(afk_channel_id: afk_channel_id)
1031
- end
1032
-
1033
1325
  # Sets the amount of time after which a user gets moved into the AFK channel.
1034
1326
  # @param afk_timeout [Integer] The AFK timeout, in seconds.
1035
1327
  def afk_timeout=(afk_timeout)
1036
1328
  update_server_data(afk_timeout: afk_timeout)
1037
1329
  end
1038
1330
 
1331
+ # Processes a GUILD_MEMBERS_CHUNK packet, specifically the members field
1332
+ # @note For internal use only
1333
+ # @!visibility private
1334
+ def process_chunk(members)
1335
+ process_members(members)
1336
+ @processed_chunk_members += members.length
1337
+ LOGGER.debug("Processed one chunk on server #{@id} - length #{members.length}")
1338
+
1339
+ # Don't bother with the rest of the method if it's not truly the last packet
1340
+ return unless @processed_chunk_members == @member_count
1341
+
1342
+ LOGGER.debug("Finished chunking server #{@id}")
1343
+
1344
+ # Reset everything to normal
1345
+ @chunked = true
1346
+ @processed_chunk_members = 0
1347
+ end
1348
+
1039
1349
  # Updates the cached data with new data
1040
1350
  # @note For internal use only
1041
1351
  # @!visibility private
1042
1352
  def update_data(new_data)
1043
1353
  @name = new_data[:name] || new_data['name'] || @name
1044
1354
  @region = new_data[:region] || new_data['region'] || @region
1045
- @icon = new_data[:icon] || new_data['icon'] || @icon
1355
+ @icon_id = new_data[:icon] || new_data['icon'] || @icon_id
1046
1356
  @afk_timeout = new_data[:afk_timeout] || new_data['afk_timeout'].to_i || @afk_timeout
1047
1357
 
1048
1358
  @afk_channel_id = new_data[:afk_channel_id] || new_data['afk_channel_id'].to_i || @afk_channel.id
1049
- @afk_channel = @bot.channel(@afk_channel_id) if @afk_channel_id != 0 && (!@afk_channel || @afk_channel_id != @afk_channel.id)
1359
+ @afk_channel = @bot.channel(@afk_channel_id, self) if @afk_channel_id != 0 && (!@afk_channel || @afk_channel_id != @afk_channel.id)
1050
1360
  end
1051
1361
 
1052
1362
  # The inspect method is overwritten to give more useful output
@@ -1060,7 +1370,7 @@ module Discordrb
1060
1370
  API.update_server(@bot.token, @id,
1061
1371
  new_data[:name] || @name,
1062
1372
  new_data[:region] || @region,
1063
- new_data[:icon] || @icon,
1373
+ new_data[:icon_id] || @icon_id,
1064
1374
  new_data[:afk_channel_id] || @afk_channel_id,
1065
1375
  new_data[:afk_timeout] || @afk_timeout)
1066
1376
  update_data(new_data)
@@ -1080,20 +1390,10 @@ module Discordrb
1080
1390
  end
1081
1391
 
1082
1392
  def process_members(members)
1083
- @members = []
1084
- @members_by_id = {}
1085
-
1086
1393
  return unless members
1087
1394
  members.each do |element|
1088
- user = User.new(element['user'], @bot)
1089
- @members << user
1090
- @members_by_id[user.id] = user
1091
- user_roles = []
1092
- element['roles'].each do |e|
1093
- role_id = e.to_i
1094
- user_roles << @roles_by_id[role_id]
1095
- end
1096
- user.update_roles(self, user_roles)
1395
+ member = Member.new(element, self, @bot)
1396
+ @members[member.id] = member
1097
1397
  end
1098
1398
  end
1099
1399
 
@@ -1103,7 +1403,7 @@ module Discordrb
1103
1403
  presences.each do |element|
1104
1404
  next unless element['user']
1105
1405
  user_id = element['user']['id'].to_i
1106
- user = @members_by_id[user_id]
1406
+ user = @members[user_id]
1107
1407
  if user
1108
1408
  user.status = element['status'].to_sym
1109
1409
  user.game = element['game'] ? element['game']['name'] : nil
@@ -1127,15 +1427,17 @@ module Discordrb
1127
1427
  return unless voice_states
1128
1428
  voice_states.each do |element|
1129
1429
  user_id = element['user_id'].to_i
1130
- user = @members_by_id[user_id]
1131
- next unless user
1132
- user.server_mute = element['mute']
1133
- user.server_deaf = element['deaf']
1134
- user.self_mute = element['self_mute']
1135
- user.self_mute = element['self_mute']
1430
+ member = @members[user_id]
1431
+ next unless member
1136
1432
  channel_id = element['channel_id'].to_i
1137
1433
  channel = channel_id ? @channels_by_id[channel_id] : nil
1138
- user.move(channel)
1434
+
1435
+ member.update_voice_state(
1436
+ channel,
1437
+ element['mute'],
1438
+ element['deaf'],
1439
+ element['self_mute'],
1440
+ element['self_deaf'])
1139
1441
  end
1140
1442
  end
1141
1443
  end