discordrb 3.3.0 → 3.4.0

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

Potentially problematic release.


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

Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +126 -0
  3. data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
  6. data/.github/pull_request_template.md +37 -0
  7. data/.rubocop.yml +34 -37
  8. data/.travis.yml +5 -6
  9. data/CHANGELOG.md +472 -347
  10. data/Gemfile +2 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +61 -79
  13. data/Rakefile +2 -0
  14. data/bin/console +1 -0
  15. data/discordrb-webhooks.gemspec +6 -6
  16. data/discordrb.gemspec +17 -17
  17. data/lib/discordrb.rb +73 -0
  18. data/lib/discordrb/allowed_mentions.rb +36 -0
  19. data/lib/discordrb/api.rb +40 -15
  20. data/lib/discordrb/api/channel.rb +57 -39
  21. data/lib/discordrb/api/invite.rb +3 -3
  22. data/lib/discordrb/api/server.rb +55 -50
  23. data/lib/discordrb/api/user.rb +8 -8
  24. data/lib/discordrb/api/webhook.rb +6 -6
  25. data/lib/discordrb/await.rb +0 -1
  26. data/lib/discordrb/bot.rb +164 -72
  27. data/lib/discordrb/cache.rb +4 -2
  28. data/lib/discordrb/colour_rgb.rb +43 -0
  29. data/lib/discordrb/commands/command_bot.rb +22 -6
  30. data/lib/discordrb/commands/container.rb +20 -23
  31. data/lib/discordrb/commands/parser.rb +18 -18
  32. data/lib/discordrb/commands/rate_limiter.rb +3 -2
  33. data/lib/discordrb/container.rb +77 -17
  34. data/lib/discordrb/data.rb +25 -4180
  35. data/lib/discordrb/data/activity.rb +264 -0
  36. data/lib/discordrb/data/application.rb +50 -0
  37. data/lib/discordrb/data/attachment.rb +56 -0
  38. data/lib/discordrb/data/audit_logs.rb +345 -0
  39. data/lib/discordrb/data/channel.rb +849 -0
  40. data/lib/discordrb/data/embed.rb +251 -0
  41. data/lib/discordrb/data/emoji.rb +82 -0
  42. data/lib/discordrb/data/integration.rb +83 -0
  43. data/lib/discordrb/data/invite.rb +137 -0
  44. data/lib/discordrb/data/member.rb +297 -0
  45. data/lib/discordrb/data/message.rb +334 -0
  46. data/lib/discordrb/data/overwrite.rb +102 -0
  47. data/lib/discordrb/data/profile.rb +91 -0
  48. data/lib/discordrb/data/reaction.rb +33 -0
  49. data/lib/discordrb/data/recipient.rb +34 -0
  50. data/lib/discordrb/data/role.rb +191 -0
  51. data/lib/discordrb/data/server.rb +1002 -0
  52. data/lib/discordrb/data/user.rb +204 -0
  53. data/lib/discordrb/data/voice_region.rb +45 -0
  54. data/lib/discordrb/data/voice_state.rb +41 -0
  55. data/lib/discordrb/data/webhook.rb +145 -0
  56. data/lib/discordrb/errors.rb +2 -1
  57. data/lib/discordrb/events/bans.rb +7 -5
  58. data/lib/discordrb/events/channels.rb +2 -0
  59. data/lib/discordrb/events/guilds.rb +16 -9
  60. data/lib/discordrb/events/invites.rb +125 -0
  61. data/lib/discordrb/events/members.rb +6 -2
  62. data/lib/discordrb/events/message.rb +69 -27
  63. data/lib/discordrb/events/presence.rb +14 -4
  64. data/lib/discordrb/events/raw.rb +1 -3
  65. data/lib/discordrb/events/reactions.rb +49 -3
  66. data/lib/discordrb/events/typing.rb +6 -4
  67. data/lib/discordrb/events/voice_server_update.rb +47 -0
  68. data/lib/discordrb/events/voice_state_update.rb +15 -10
  69. data/lib/discordrb/events/webhooks.rb +9 -6
  70. data/lib/discordrb/gateway.rb +72 -57
  71. data/lib/discordrb/id_object.rb +39 -0
  72. data/lib/discordrb/light/integrations.rb +1 -1
  73. data/lib/discordrb/light/light_bot.rb +1 -1
  74. data/lib/discordrb/logger.rb +4 -4
  75. data/lib/discordrb/paginator.rb +57 -0
  76. data/lib/discordrb/permissions.rb +103 -8
  77. data/lib/discordrb/version.rb +1 -1
  78. data/lib/discordrb/voice/encoder.rb +3 -3
  79. data/lib/discordrb/voice/network.rb +84 -43
  80. data/lib/discordrb/voice/sodium.rb +96 -0
  81. data/lib/discordrb/voice/voice_bot.rb +34 -26
  82. metadata +93 -55
@@ -0,0 +1,1002 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # Basic attributes a server should have
5
+ module ServerAttributes
6
+ # @return [String] this server's name.
7
+ attr_reader :name
8
+
9
+ # @return [String] the hexadecimal ID used to identify this server's icon.
10
+ attr_reader :icon_id
11
+
12
+ # Utility function to get the URL for the icon image
13
+ # @return [String] the URL to the icon image
14
+ def icon_url
15
+ return nil unless @icon_id
16
+
17
+ API.icon_url(@id, @icon_id)
18
+ end
19
+ end
20
+
21
+ # A server on Discord
22
+ class Server
23
+ include IDObject
24
+ include ServerAttributes
25
+
26
+ # @return [String] the ID of the region the server is on (e.g. `amsterdam`).
27
+ attr_reader :region_id
28
+
29
+ # @return [Array<Channel>] an array of all the channels (text and voice) on this server.
30
+ attr_reader :channels
31
+
32
+ # @return [Array<Role>] an array of all the roles created on this server.
33
+ attr_reader :roles
34
+
35
+ # @return [Hash<Integer => Emoji>] a hash of all the emoji available on this server.
36
+ attr_reader :emoji
37
+ alias_method :emojis, :emoji
38
+
39
+ # @return [true, false] whether or not this server is large (members > 100). If it is,
40
+ # it means the members list may be inaccurate for a couple seconds after starting up the bot.
41
+ attr_reader :large
42
+ alias_method :large?, :large
43
+
44
+ # @return [Array<Symbol>] the features of the server (eg. "INVITE_SPLASH")
45
+ attr_reader :features
46
+
47
+ # @return [Integer] the absolute number of members on this server, offline or not.
48
+ attr_reader :member_count
49
+
50
+ # @return [Integer] the amount of time after which a voice user gets moved into the AFK channel, in seconds.
51
+ attr_reader :afk_timeout
52
+
53
+ # @return [Hash<Integer => VoiceState>] the hash (user ID => voice state) of voice states of members on this server
54
+ attr_reader :voice_states
55
+
56
+ # The server's amount of Nitro boosters.
57
+ # @return [Integer] the amount of boosters, 0 if no one has boosted.
58
+ attr_reader :booster_count
59
+
60
+ # The server's Nitro boost level.
61
+ # @return [Integer] the boost level, 0 if no level.
62
+ attr_reader :boost_level
63
+
64
+ # @!visibility private
65
+ def initialize(data, bot)
66
+ @bot = bot
67
+ @owner_id = data['owner_id'].to_i
68
+ @id = data['id'].to_i
69
+
70
+ process_channels(data['channels'])
71
+ update_data(data)
72
+
73
+ @large = data['large']
74
+ @member_count = data['member_count']
75
+ @splash_id = nil
76
+ @banner_id = nil
77
+ @features = data['features'].map { |element| element.downcase.to_sym }
78
+ @members = {}
79
+ @voice_states = {}
80
+ @emoji = {}
81
+
82
+ process_roles(data['roles'])
83
+ process_emoji(data['emojis'])
84
+ process_members(data['members'])
85
+ process_presences(data['presences'])
86
+ process_voice_states(data['voice_states'])
87
+
88
+ # Whether this server's members have been chunked (resolved using op 8 and GUILD_MEMBERS_CHUNK) yet
89
+ @chunked = false
90
+ @processed_chunk_members = 0
91
+
92
+ @booster_count = data['premium_subscription_count'] || 0
93
+ @boost_level = data['premium_tier']
94
+ end
95
+
96
+ # @return [Member] The server owner.
97
+ def owner
98
+ @owner ||= member(@owner_id)
99
+ end
100
+
101
+ # The default channel is the text channel on this server with the highest position
102
+ # that the bot has Read Messages permission on.
103
+ # @param send_messages [true, false] whether to additionally consider if the bot has Send Messages permission
104
+ # @return [Channel, nil] The default channel on this server, or `nil` if there are no channels that the bot can read.
105
+ def default_channel(send_messages = false)
106
+ bot_member = member(@bot.profile)
107
+ text_channels.sort_by { |e| [e.position, e.id] }.find do |e|
108
+ if send_messages
109
+ bot_member.can_read_messages?(e) && bot_member.can_send_messages?(e)
110
+ else
111
+ bot_member.can_read_messages?(e)
112
+ end
113
+ end
114
+ end
115
+
116
+ alias_method :general_channel, :default_channel
117
+
118
+ # @return [Role] The @everyone role on this server
119
+ def everyone_role
120
+ role(@id)
121
+ end
122
+
123
+ # Gets a role on this server based on its ID.
124
+ # @param id [String, Integer] The role ID to look for.
125
+ # @return [Role, nil] The role identified by the ID, or `nil` if it couldn't be found.
126
+ def role(id)
127
+ id = id.resolve_id
128
+ @roles.find { |e| e.id == id }
129
+ end
130
+
131
+ # Gets a member on this server based on user ID
132
+ # @param id [Integer] The user ID to look for
133
+ # @param request [true, false] Whether the member should be requested from Discord if it's not cached
134
+ def member(id, request = true)
135
+ id = id.resolve_id
136
+ return @members[id] if member_cached?(id)
137
+ return nil unless request
138
+
139
+ member = @bot.member(self, id)
140
+ @members[id] = member unless member.nil?
141
+ rescue StandardError
142
+ nil
143
+ end
144
+
145
+ # @return [Array<Member>] an array of all the members on this server.
146
+ def members
147
+ return @members.values if @chunked
148
+
149
+ @bot.debug("Members for server #{@id} not chunked yet - initiating")
150
+ @bot.request_chunks(@id)
151
+ sleep 0.05 until @chunked
152
+ @members.values
153
+ end
154
+
155
+ alias_method :users, :members
156
+
157
+ # @return [Array<Member>] an array of all the bot members on this server.
158
+ def bot_members
159
+ members.select(&:bot_account?)
160
+ end
161
+
162
+ # @return [Array<Member>] an array of all the non bot members on this server.
163
+ def non_bot_members
164
+ members.reject(&:bot_account?)
165
+ end
166
+
167
+ # @return [Member] the bot's own `Member` on this server
168
+ def bot
169
+ member(@bot.profile)
170
+ end
171
+
172
+ # @return [Array<Integration>] an array of all the integrations connected to this server.
173
+ def integrations
174
+ integration = JSON.parse(API::Server.integrations(@bot.token, @id))
175
+ integration.map { |element| Integration.new(element, @bot, self) }
176
+ end
177
+
178
+ # @param action [Symbol] The action to only include.
179
+ # @param user [User, String, Integer] The user, or their ID, to filter entries to.
180
+ # @param limit [Integer] The amount of entries to limit it to.
181
+ # @param before [Entry, String, Integer] The entry, or its ID, to use to not include all entries after it.
182
+ # @return [AuditLogs] The server's audit logs.
183
+ def audit_logs(action: nil, user: nil, limit: 50, before: nil)
184
+ raise 'Invalid audit log action!' if action && AuditLogs::ACTIONS.key(action).nil?
185
+
186
+ action = AuditLogs::ACTIONS.key(action)
187
+ user = user.resolve_id if user
188
+ before = before.resolve_id if before
189
+ AuditLogs.new(self, @bot, JSON.parse(API::Server.audit_logs(@bot.token, @id, limit, user, action, before)))
190
+ end
191
+
192
+ # Cache @embed
193
+ # @note For internal use only
194
+ # @!visibility private
195
+ def cache_embed_data
196
+ data = JSON.parse(API::Server.embed(@bot.token, @id))
197
+ @embed_enabled = data['enabled']
198
+ @embed_channel_id = data['channel_id']
199
+ end
200
+
201
+ # @return [true, false] whether or not the server has widget enabled
202
+ def embed_enabled?
203
+ cache_embed_data if @embed_enabled.nil?
204
+ @embed_enabled
205
+ end
206
+ alias_method :widget_enabled, :embed_enabled?
207
+ alias_method :widget?, :embed_enabled?
208
+ alias_method :embed?, :embed_enabled?
209
+
210
+ # @return [Channel, nil] the channel the server embed will make an invite for.
211
+ def embed_channel
212
+ cache_embed_data if @embed_enabled.nil?
213
+ @bot.channel(@embed_channel_id) if @embed_channel_id
214
+ end
215
+ alias_method :widget_channel, :embed_channel
216
+
217
+ # Sets whether this server's embed (widget) is enabled
218
+ # @param value [true, false]
219
+ def embed_enabled=(value)
220
+ modify_embed(value, embed_channel)
221
+ end
222
+
223
+ alias_method :widget_enabled=, :embed_enabled=
224
+
225
+ # Sets whether this server's embed (widget) is enabled
226
+ # @param value [true, false]
227
+ # @param reason [String, nil] the reason to be shown in the audit log for this action
228
+ def set_embed_enabled(value, reason = nil)
229
+ modify_embed(value, embed_channel, reason)
230
+ end
231
+
232
+ alias_method :set_widget_enabled, :set_embed_enabled
233
+
234
+ # Changes the channel on the server's embed (widget)
235
+ # @param channel [Channel, String, Integer] the channel, or its ID, to be referenced by the embed
236
+ def embed_channel=(channel)
237
+ modify_embed(embed?, channel)
238
+ end
239
+
240
+ alias_method :widget_channel=, :embed_channel=
241
+
242
+ # Changes the channel on the server's embed (widget)
243
+ # @param channel [Channel, String, Integer] the channel, or its ID, to be referenced by the embed
244
+ # @param reason [String, nil] the reason to be shown in the audit log for this action
245
+ def set_embed_channel(channel, reason = nil)
246
+ modify_embed(embed?, channel, reason)
247
+ end
248
+
249
+ alias_method :set_widget_channel, :set_embed_channel
250
+
251
+ # Changes the channel on the server's embed (widget), and sets whether it is enabled.
252
+ # @param enabled [true, false] whether the embed (widget) is enabled
253
+ # @param channel [Channel, String, Integer] the channel, or its ID, to be referenced by the embed
254
+ # @param reason [String, nil] the reason to be shown in the audit log for this action
255
+ def modify_embed(enabled, channel, reason = nil)
256
+ cache_embed_data if @embed_enabled.nil?
257
+ channel_id = channel ? channel.resolve_id : @embed_channel_id
258
+ response = JSON.parse(API::Server.modify_embed(@bot.token, @id, enabled, channel_id, reason))
259
+ @embed_enabled = response['enabled']
260
+ @embed_channel_id = response['channel_id']
261
+ end
262
+
263
+ alias_method :modify_widget, :modify_embed
264
+
265
+ # @param include_idle [true, false] Whether to count idle members as online.
266
+ # @param include_bots [true, false] Whether to include bot accounts in the count.
267
+ # @return [Array<Member>] an array of online members on this server.
268
+ def online_members(include_idle: false, include_bots: true)
269
+ @members.values.select do |e|
270
+ ((include_idle ? e.idle? : false) || e.online?) && (include_bots ? true : !e.bot_account?)
271
+ end
272
+ end
273
+
274
+ alias_method :online_users, :online_members
275
+
276
+ # Adds a member to this guild that has granted this bot's application an OAuth2 access token
277
+ # with the `guilds.join` scope.
278
+ # For more information about Discord's OAuth2 implementation, see: https://discord.com/developers/docs/topics/oauth2
279
+ # @note Your bot must be present in this server, and have permission to create instant invites for this to work.
280
+ # @param user [User, String, Integer] the user, or ID of the user to add to this server
281
+ # @param access_token [String] the OAuth2 Bearer token that has been granted the `guilds.join` scope
282
+ # @param nick [String] the nickname to give this member upon joining
283
+ # @param roles [Role, Array<Role, String, Integer>] the role (or roles) to give this member upon joining
284
+ # @param deaf [true, false] whether this member will be server deafened upon joining
285
+ # @param mute [true, false] whether this member will be server muted upon joining
286
+ # @return [Member, nil] the created member, or `nil` if the user is already a member of this server.
287
+ def add_member_using_token(user, access_token, nick: nil, roles: [], deaf: false, mute: false)
288
+ user_id = user.resolve_id
289
+ roles = roles.is_a?(Array) ? roles.map(&:resolve_id) : [roles.resolve_id]
290
+ response = API::Server.add_member(@bot.token, @id, user_id, access_token, nick, roles, deaf, mute)
291
+ return nil if response.empty?
292
+
293
+ add_member Member.new(JSON.parse(response), self, @bot)
294
+ end
295
+
296
+ # Returns the amount of members that are candidates for pruning
297
+ # @param days [Integer] the number of days to consider for inactivity
298
+ # @return [Integer] number of members to be removed
299
+ # @raise [ArgumentError] if days is not between 1 and 30 (inclusive)
300
+ def prune_count(days)
301
+ raise ArgumentError, 'Days must be between 1 and 30' unless days.between?(1, 30)
302
+
303
+ response = JSON.parse API::Server.prune_count(@bot.token, @id, days)
304
+ response['pruned']
305
+ end
306
+
307
+ # Prunes (kicks) an amount of members for inactivity
308
+ # @param days [Integer] the number of days to consider for inactivity (between 1 and 30)
309
+ # @param reason [String] The reason the for the prune.
310
+ # @return [Integer] the number of members removed at the end of the operation
311
+ # @raise [ArgumentError] if days is not between 1 and 30 (inclusive)
312
+ def begin_prune(days, reason = nil)
313
+ raise ArgumentError, 'Days must be between 1 and 30' unless days.between?(1, 30)
314
+
315
+ response = JSON.parse API::Server.begin_prune(@bot.token, @id, days, reason)
316
+ response['pruned']
317
+ end
318
+
319
+ alias_method :prune, :begin_prune
320
+
321
+ # @return [Array<Channel>] an array of text channels on this server
322
+ def text_channels
323
+ @channels.select(&:text?)
324
+ end
325
+
326
+ # @return [Array<Channel>] an array of voice channels on this server
327
+ def voice_channels
328
+ @channels.select(&:voice?)
329
+ end
330
+
331
+ # @return [Array<Channel>] an array of category channels on this server
332
+ def categories
333
+ @channels.select(&:category?)
334
+ end
335
+
336
+ # @return [Array<Channel>] an array of channels on this server that are not in a category
337
+ def orphan_channels
338
+ @channels.reject { |c| c.parent || c.category? }
339
+ end
340
+
341
+ # @return [String, nil] the widget URL to the server that displays the amount of online members in a
342
+ # stylish way. `nil` if the widget is not enabled.
343
+ def widget_url
344
+ update_data if @embed_enabled.nil?
345
+ return unless @embed_enabled
346
+
347
+ API.widget_url(@id)
348
+ end
349
+
350
+ # @param style [Symbol] The style the picture should have. Possible styles are:
351
+ # * `: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.
352
+ # * `: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.
353
+ # * `: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.
354
+ # * `: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.
355
+ # * `: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.
356
+ # @return [String, nil] the widget banner URL to the server that displays the amount of online members,
357
+ # server icon and server name in a stylish way. `nil` if the widget is not enabled.
358
+ def widget_banner_url(style)
359
+ update_data if @embed_enabled.nil?
360
+ return unless @embed_enabled
361
+
362
+ API.widget_url(@id, style)
363
+ end
364
+
365
+ # @return [String] the hexadecimal ID used to identify this server's splash image for their VIP invite page.
366
+ def splash_id
367
+ @splash_id ||= JSON.parse(API::Server.resolve(@bot.token, @id))['splash']
368
+ end
369
+ alias splash_hash splash_id
370
+
371
+ # @return [String, nil] the splash image URL for the server's VIP invite page.
372
+ # `nil` if there is no splash image.
373
+ def splash_url
374
+ splash_id if @splash_id.nil?
375
+ return nil unless @splash_id
376
+
377
+ API.splash_url(@id, @splash_id)
378
+ end
379
+
380
+ # @return [String] the hexadecimal ID used to identify this server's banner image, shown by the server name.
381
+ def banner_id
382
+ @banner_id ||= JSON.parse(API::Server.resolve(@bot.token, @id))['banner']
383
+ end
384
+
385
+ # @return [String, nil] the banner image URL for the server's banner image, or
386
+ # `nil` if there is no banner image.
387
+ def banner_url
388
+ banner_id if @banner_id.nil?
389
+ return unless banner_id
390
+
391
+ API.banner_url(@id, @banner_id)
392
+ end
393
+
394
+ # @return [String] a URL that a user can use to navigate to this server in the client
395
+ def link
396
+ "https://discord.com/channels/#{@id}"
397
+ end
398
+
399
+ alias_method :jump_link, :link
400
+
401
+ # Adds a role to the role cache
402
+ # @note For internal use only
403
+ # @!visibility private
404
+ def add_role(role)
405
+ @roles << role
406
+ end
407
+
408
+ # Removes a role from the role cache
409
+ # @note For internal use only
410
+ # @!visibility private
411
+ def delete_role(role_id)
412
+ @roles.reject! { |r| r.id == role_id }
413
+ @members.each do |_, member|
414
+ new_roles = member.roles.reject { |r| r.id == role_id }
415
+ member.update_roles(new_roles)
416
+ end
417
+ @channels.each do |channel|
418
+ overwrites = channel.permission_overwrites.reject { |id, _| id == role_id }
419
+ channel.update_overwrites(overwrites)
420
+ end
421
+ end
422
+
423
+ # Updates the positions of all roles on the server
424
+ # @note For internal use only
425
+ # @!visibility private
426
+ def update_role_positions(role_positions)
427
+ response = JSON.parse(API::Server.update_role_positions(@bot.token, @id, role_positions))
428
+ response.each do |data|
429
+ updated_role = Role.new(data, @bot, self)
430
+ role(updated_role.id).update_from(updated_role)
431
+ end
432
+ end
433
+
434
+ # Adds a member to the member cache.
435
+ # @note For internal use only
436
+ # @!visibility private
437
+ def add_member(member)
438
+ @member_count += 1
439
+ @members[member.id] = member
440
+ end
441
+
442
+ # Removes a member from the member cache.
443
+ # @note For internal use only
444
+ # @!visibility private
445
+ def delete_member(user_id)
446
+ @members.delete(user_id)
447
+ @member_count -= 1
448
+ end
449
+
450
+ # Checks whether a member is cached
451
+ # @note For internal use only
452
+ # @!visibility private
453
+ def member_cached?(user_id)
454
+ @members.include?(user_id)
455
+ end
456
+
457
+ # Adds a member to the cache
458
+ # @note For internal use only
459
+ # @!visibility private
460
+ def cache_member(member)
461
+ @members[member.id] = member
462
+ end
463
+
464
+ # Updates a member's voice state
465
+ # @note For internal use only
466
+ # @!visibility private
467
+ def update_voice_state(data)
468
+ user_id = data['user_id'].to_i
469
+
470
+ if data['channel_id']
471
+ unless @voice_states[user_id]
472
+ # Create a new voice state for the user
473
+ @voice_states[user_id] = VoiceState.new(user_id)
474
+ end
475
+
476
+ # Update the existing voice state (or the one we just created)
477
+ channel = @channels_by_id[data['channel_id'].to_i]
478
+ @voice_states[user_id].update(
479
+ channel,
480
+ data['mute'],
481
+ data['deaf'],
482
+ data['self_mute'],
483
+ data['self_deaf']
484
+ )
485
+ else
486
+ # The user is not in a voice channel anymore, so delete its voice state
487
+ @voice_states.delete(user_id)
488
+ end
489
+ end
490
+
491
+ # Creates a channel on this server with the given name.
492
+ # @note If parent is provided, permission overwrites have the follow behavior:
493
+ #
494
+ # 1. If overwrites is null, the new channel inherits the parent's permissions.
495
+ # 2. If overwrites is [], the new channel inherits the parent's permissions.
496
+ # 3. If you supply one or more overwrites, the channel will be created with those permissions and ignore the parents.
497
+ #
498
+ # @param name [String] Name of the channel to create
499
+ # @param type [Integer, Symbol] Type of channel to create (0: text, 2: voice, 4: category, 5: news, 6: store)
500
+ # @param topic [String] the topic of this channel, if it will be a text channel
501
+ # @param bitrate [Integer] the bitrate of this channel, if it will be a voice channel
502
+ # @param user_limit [Integer] the user limit of this channel, if it will be a voice channel
503
+ # @param permission_overwrites [Array<Hash>, Array<Overwrite>] permission overwrites for this channel
504
+ # @param parent [Channel, String, Integer] parent category, or its ID, for this channel to be created in.
505
+ # @param nsfw [true, false] whether this channel should be created as nsfw
506
+ # @param rate_limit_per_user [Integer] how many seconds users need to wait in between messages.
507
+ # @param reason [String] The reason the for the creation of this channel.
508
+ # @return [Channel] the created channel.
509
+ # @raise [ArgumentError] if type is not 0 (text), 2 (voice), 4 (category), 5 (news), or 6 (store)
510
+ def create_channel(name, type = 0, topic: nil, bitrate: nil, user_limit: nil, permission_overwrites: nil, parent: nil, nsfw: false, rate_limit_per_user: nil, position: nil, reason: nil)
511
+ type = Channel::TYPES[type] if type.is_a?(Symbol)
512
+ raise ArgumentError, 'Channel type must be either 0 (text), 2 (voice), 4 (category), news (5), or store (6)!' unless [0, 2, 4, 5, 6].include?(type)
513
+
514
+ permission_overwrites.map! { |e| e.is_a?(Overwrite) ? e.to_hash : e } if permission_overwrites.is_a?(Array)
515
+ parent_id = parent.respond_to?(:resolve_id) ? parent.resolve_id : nil
516
+ response = API::Server.create_channel(@bot.token, @id, name, type, topic, bitrate, user_limit, permission_overwrites, parent_id, nsfw, rate_limit_per_user, position, reason)
517
+ Channel.new(JSON.parse(response), @bot)
518
+ end
519
+
520
+ # Creates a role on this server which can then be modified. It will be initialized
521
+ # with the regular role defaults the client uses unless specified, i.e. name is "new role",
522
+ # permissions are the default, colour is the default etc.
523
+ # @param name [String] Name of the role to create
524
+ # @param colour [Integer, ColourRGB, #combined] The roles colour
525
+ # @param hoist [true, false]
526
+ # @param mentionable [true, false]
527
+ # @param permissions [Integer, Array<Symbol>, Permissions, #bits] The permissions to write to the new role.
528
+ # @param reason [String] The reason the for the creation of this role.
529
+ # @return [Role] the created role.
530
+ def create_role(name: 'new role', colour: 0, hoist: false, mentionable: false, permissions: 104_324_161, reason: nil)
531
+ colour = colour.respond_to?(:combined) ? colour.combined : colour
532
+
533
+ permissions = if permissions.is_a?(Array)
534
+ Permissions.bits(permissions)
535
+ elsif permissions.respond_to?(:bits)
536
+ permissions.bits
537
+ else
538
+ permissions
539
+ end
540
+
541
+ response = API::Server.create_role(@bot.token, @id, name, colour, hoist, mentionable, permissions, reason)
542
+
543
+ role = Role.new(JSON.parse(response), @bot, self)
544
+ @roles << role
545
+ role
546
+ end
547
+
548
+ # Adds a new custom emoji on this server.
549
+ # @param name [String] The name of emoji to create.
550
+ # @param image [String, #read] A base64 encoded string with the image data, or an object that responds to `#read`, such as `File`.
551
+ # @param roles [Array<Role, String, Integer>] An array of roles, or role IDs to be whitelisted for this emoji.
552
+ # @param reason [String] The reason the for the creation of this emoji.
553
+ # @return [Emoji] The emoji that has been added.
554
+ def add_emoji(name, image, roles = [], reason: nil)
555
+ image_string = image
556
+ if image.respond_to? :read
557
+ image_string = 'data:image/jpg;base64,'
558
+ image_string += Base64.strict_encode64(image.read)
559
+ end
560
+
561
+ data = JSON.parse(API::Server.add_emoji(@bot.token, @id, image_string, name, roles.map(&:resolve_id), reason))
562
+ new_emoji = Emoji.new(data, @bot, self)
563
+ @emoji[new_emoji.id] = new_emoji
564
+ end
565
+
566
+ # Delete a custom emoji on this server
567
+ # @param emoji [Emoji, String, Integer] The emoji or emoji ID to be deleted.
568
+ # @param reason [String] The reason the for the deletion of this emoji.
569
+ def delete_emoji(emoji, reason: nil)
570
+ API::Server.delete_emoji(@bot.token, @id, emoji.resolve_id, reason)
571
+ end
572
+
573
+ # Changes the name and/or role whitelist of an emoji on this server.
574
+ # @param emoji [Emoji, String, Integer] The emoji or emoji ID to edit.
575
+ # @param name [String] The new name for the emoji.
576
+ # @param roles [Array<Role, String, Integer>] A new array of roles, or role IDs, to whitelist.
577
+ # @param reason [String] The reason for the editing of this emoji.
578
+ # @return [Emoji] The edited emoji.
579
+ def edit_emoji(emoji, name: nil, roles: nil, reason: nil)
580
+ emoji = @emoji[emoji.resolve_id]
581
+ data = JSON.parse(API::Server.edit_emoji(@bot.token, @id, emoji.resolve_id, name || emoji.name, (roles || emoji.roles).map(&:resolve_id), reason))
582
+ new_emoji = Emoji.new(data, @bot, self)
583
+ @emoji[new_emoji.id] = new_emoji
584
+ end
585
+
586
+ # The amount of emoji the server can have, based on its current Nitro Boost Level.
587
+ # @return [Integer] the max amount of emoji
588
+ def max_emoji
589
+ case @level
590
+ when 1
591
+ 100
592
+ when 2
593
+ 150
594
+ when 3
595
+ 250
596
+ else
597
+ 50
598
+ end
599
+ end
600
+
601
+ # @return [Array<ServerBan>] a list of banned users on this server and the reason they were banned.
602
+ def bans
603
+ response = JSON.parse(API::Server.bans(@bot.token, @id))
604
+ response.map do |e|
605
+ ServerBan.new(self, User.new(e['user'], @bot), e['reason'])
606
+ end
607
+ end
608
+
609
+ # Bans a user from this server.
610
+ # @param user [User, String, Integer] The user to ban.
611
+ # @param message_days [Integer] How many days worth of messages sent by the user should be deleted.
612
+ # @param reason [String] The reason the user is being banned.
613
+ def ban(user, message_days = 0, reason: nil)
614
+ API::Server.ban_user(@bot.token, @id, user.resolve_id, message_days, reason)
615
+ end
616
+
617
+ # Unbans a previously banned user from this server.
618
+ # @param user [User, String, Integer] The user to unban.
619
+ # @param reason [String] The reason the user is being unbanned.
620
+ def unban(user, reason = nil)
621
+ API::Server.unban_user(@bot.token, @id, user.resolve_id, reason)
622
+ end
623
+
624
+ # Kicks a user from this server.
625
+ # @param user [User, String, Integer] The user to kick.
626
+ # @param reason [String] The reason the user is being kicked.
627
+ def kick(user, reason = nil)
628
+ API::Server.remove_member(@bot.token, @id, user.resolve_id, reason)
629
+ end
630
+
631
+ # Forcibly moves a user into a different voice channel. Only works if the bot has the permission needed.
632
+ # @param user [User, String, Integer] The user to move.
633
+ # @param channel [Channel, String, Integer] The voice channel to move into.
634
+ def move(user, channel)
635
+ API::Server.update_member(@bot.token, @id, user.resolve_id, channel_id: channel.resolve_id)
636
+ end
637
+
638
+ # Deletes this server. Be aware that this is permanent and impossible to undo, so be careful!
639
+ def delete
640
+ API::Server.delete(@bot.token, @id)
641
+ end
642
+
643
+ # Leave the server.
644
+ def leave
645
+ API::User.leave_server(@bot.token, @id)
646
+ end
647
+
648
+ # Transfers server ownership to another user.
649
+ # @param user [User, String, Integer] The user who should become the new owner.
650
+ def owner=(user)
651
+ API::Server.transfer_ownership(@bot.token, @id, user.resolve_id)
652
+ end
653
+
654
+ # Sets the server's name.
655
+ # @param name [String] The new server name.
656
+ def name=(name)
657
+ update_server_data(name: name)
658
+ end
659
+
660
+ # @return [Array<VoiceRegion>] collection of available voice regions to this guild
661
+ def available_voice_regions
662
+ return @available_voice_regions if @available_voice_regions
663
+
664
+ @available_voice_regions = {}
665
+
666
+ data = JSON.parse API::Server.regions(@bot.token, @id)
667
+ @available_voice_regions = data.map { |e| VoiceRegion.new e }
668
+ end
669
+
670
+ # @return [VoiceRegion, nil] voice region data for this server's region
671
+ # @note This may return `nil` if this server's voice region is deprecated.
672
+ def region
673
+ available_voice_regions.find { |e| e.id == @region_id }
674
+ end
675
+
676
+ # Moves the server to another region. This will cause a voice interruption of at most a second.
677
+ # @param region [String] The new region the server should be in.
678
+ def region=(region)
679
+ update_server_data(region: region.to_s)
680
+ end
681
+
682
+ # Sets the server's icon.
683
+ # @param icon [String, #read] The new icon, in base64-encoded JPG format.
684
+ def icon=(icon)
685
+ if icon.respond_to? :read
686
+ icon_string = 'data:image/jpg;base64,'
687
+ icon_string += Base64.strict_encode64(icon.read)
688
+ update_server_data(icon_id: icon_string)
689
+ else
690
+ update_server_data(icon_id: icon)
691
+ end
692
+ end
693
+
694
+ # Sets the server's AFK channel.
695
+ # @param afk_channel [Channel, nil] The new AFK channel, or `nil` if there should be none set.
696
+ def afk_channel=(afk_channel)
697
+ update_server_data(afk_channel_id: afk_channel.resolve_id)
698
+ end
699
+
700
+ # Sets the server's system channel.
701
+ # @param system_channel [Channel, String, Integer, nil] The new system channel, or `nil` should it be disabled.
702
+ def system_channel=(system_channel)
703
+ update_server_data(system_channel_id: system_channel.resolve_id)
704
+ end
705
+
706
+ # Sets the amount of time after which a user gets moved into the AFK channel.
707
+ # @param afk_timeout [Integer] The AFK timeout, in seconds.
708
+ def afk_timeout=(afk_timeout)
709
+ update_server_data(afk_timeout: afk_timeout)
710
+ end
711
+
712
+ # A map of possible server verification levels to symbol names
713
+ VERIFICATION_LEVELS = {
714
+ none: 0,
715
+ low: 1,
716
+ medium: 2,
717
+ high: 3,
718
+ very_high: 4
719
+ }.freeze
720
+
721
+ # @return [Symbol] the verification level of the server (:none = none, :low = 'Must have a verified email on their Discord account', :medium = 'Has to be registered with Discord for at least 5 minutes', :high = 'Has to be a member of this server for at least 10 minutes', :very_high = 'Must have a verified phone on their Discord account').
722
+ def verification_level
723
+ VERIFICATION_LEVELS.key @verification_level
724
+ end
725
+
726
+ # Sets the verification level of the server
727
+ # @param level [Integer, Symbol] The verification level from 0-4 or Symbol (see {VERIFICATION_LEVELS})
728
+ def verification_level=(level)
729
+ level = VERIFICATION_LEVELS[level] if level.is_a?(Symbol)
730
+
731
+ update_server_data(verification_level: level)
732
+ end
733
+
734
+ # A map of possible message notification levels to symbol names
735
+ NOTIFICATION_LEVELS = {
736
+ all_messages: 0,
737
+ only_mentions: 1
738
+ }.freeze
739
+
740
+ # @return [Symbol] the default message notifications settings of the server (:all = 'All messages', :mentions = 'Only @mentions').
741
+ def default_message_notifications
742
+ NOTIFICATION_LEVELS.key @default_message_notifications
743
+ end
744
+
745
+ # Sets the default message notification level
746
+ # @param notification_level [Integer, Symbol] The default message notification 0-1 or Symbol (see {NOTIFICATION_LEVELS})
747
+ def default_message_notifications=(notification_level)
748
+ notification_level = NOTIFICATION_LEVELS[notification_level] if notification_level.is_a?(Symbol)
749
+
750
+ update_server_data(default_message_notifications: notification_level)
751
+ end
752
+
753
+ alias_method :notification_level=, :default_message_notifications=
754
+
755
+ # Sets the server splash
756
+ # @param splash_hash [String] The splash hash
757
+ def splash=(splash_hash)
758
+ update_server_data(splash: splash_hash)
759
+ end
760
+
761
+ # A map of possible content filter levels to symbol names
762
+ FILTER_LEVELS = {
763
+ disabled: 0,
764
+ members_without_roles: 1,
765
+ all_members: 2
766
+ }.freeze
767
+
768
+ # @return [Symbol] the explicit content filter level of the server (:none = 'Don't scan any messages.', :exclude_roles = 'Scan messages for members without a role.', :all = 'Scan messages sent by all members.').
769
+ def explicit_content_filter
770
+ FILTER_LEVELS.key @explicit_content_filter
771
+ end
772
+
773
+ alias_method :content_filter_level, :explicit_content_filter
774
+
775
+ # Sets the server content filter.
776
+ # @param filter_level [Integer, Symbol] The content filter from 0-2 or Symbol (see {FILTER_LEVELS})
777
+ def explicit_content_filter=(filter_level)
778
+ filter_level = FILTER_LEVELS[filter_level] if filter_level.is_a?(Symbol)
779
+
780
+ update_server_data(explicit_content_filter: filter_level)
781
+ end
782
+
783
+ # @return [true, false] whether this server has any emoji or not.
784
+ def any_emoji?
785
+ @emoji.any?
786
+ end
787
+
788
+ alias_method :has_emoji?, :any_emoji?
789
+ alias_method :emoji?, :any_emoji?
790
+
791
+ # Requests a list of Webhooks on the server.
792
+ # @return [Array<Webhook>] webhooks on the server.
793
+ def webhooks
794
+ webhooks = JSON.parse(API::Server.webhooks(@bot.token, @id))
795
+ webhooks.map { |webhook| Webhook.new(webhook, @bot) }
796
+ end
797
+
798
+ # Requests a list of Invites to the server.
799
+ # @return [Array<Invite>] invites to the server.
800
+ def invites
801
+ invites = JSON.parse(API::Server.invites(@bot.token, @id))
802
+ invites.map { |invite| Invite.new(invite, @bot) }
803
+ end
804
+
805
+ # Processes a GUILD_MEMBERS_CHUNK packet, specifically the members field
806
+ # @note For internal use only
807
+ # @!visibility private
808
+ def process_chunk(members)
809
+ process_members(members)
810
+ @processed_chunk_members += members.length
811
+ LOGGER.debug("Processed one chunk on server #{@id} - length #{members.length}")
812
+
813
+ # Don't bother with the rest of the method if it's not truly the last packet
814
+ return unless @processed_chunk_members == @member_count
815
+
816
+ LOGGER.debug("Finished chunking server #{@id}")
817
+
818
+ # Reset everything to normal
819
+ @chunked = true
820
+ @processed_chunk_members = 0
821
+ end
822
+
823
+ # @return [Channel, nil] the AFK voice channel of this server, or `nil` if none is set.
824
+ def afk_channel
825
+ @bot.channel(@afk_channel_id) if @afk_channel_id
826
+ end
827
+
828
+ # @return [Channel, nil] the system channel (used for automatic welcome messages) of a server, or `nil` if none is set.
829
+ def system_channel
830
+ @bot.channel(@system_channel_id) if @system_channel_id
831
+ end
832
+
833
+ # Updates the cached data with new data
834
+ # @note For internal use only
835
+ # @!visibility private
836
+ def update_data(new_data = nil)
837
+ new_data ||= JSON.parse(API::Server.resolve(@bot.token, @id))
838
+ @name = new_data[:name] || new_data['name'] || @name
839
+ @region_id = new_data[:region] || new_data['region'] || @region_id
840
+ @icon_id = new_data[:icon] || new_data['icon'] || @icon_id
841
+ @afk_timeout = new_data[:afk_timeout] || new_data['afk_timeout'] || @afk_timeout
842
+
843
+ afk_channel_id = new_data[:afk_channel_id] || new_data['afk_channel_id'] || @afk_channel
844
+ @afk_channel_id = afk_channel_id.nil? ? nil : afk_channel_id.resolve_id
845
+ embed_channel_id = new_data[:embed_channel_id] || new_data['embed_channel_id'] || @embed_channel
846
+ @embed_channel_id = embed_channel_id.nil? ? nil : embed_channel_id.resolve_id
847
+ system_channel_id = new_data[:system_channel_id] || new_data['system_channel_id'] || @system_channel
848
+ @system_channel_id = system_channel_id.nil? ? nil : system_channel_id.resolve_id
849
+
850
+ @embed_enabled = new_data[:embed_enabled] || new_data['embed_enabled']
851
+ @splash = new_data[:splash_id] || new_data['splash_id'] || @splash_id
852
+
853
+ @verification_level = new_data[:verification_level] || new_data['verification_level'] || @verification_level
854
+ @explicit_content_filter = new_data[:explicit_content_filter] || new_data['explicit_content_filter'] || @explicit_content_filter
855
+ @default_message_notifications = new_data[:default_message_notifications] || new_data['default_message_notifications'] || @default_message_notifications
856
+ end
857
+
858
+ # Adds a channel to this server's cache
859
+ # @note For internal use only
860
+ # @!visibility private
861
+ def add_channel(channel)
862
+ @channels << channel
863
+ @channels_by_id[channel.id] = channel
864
+ end
865
+
866
+ # Deletes a channel from this server's cache
867
+ # @note For internal use only
868
+ # @!visibility private
869
+ def delete_channel(id)
870
+ @channels.reject! { |e| e.id == id }
871
+ @channels_by_id.delete(id)
872
+ end
873
+
874
+ # Updates the cached emoji data with new data
875
+ # @note For internal use only
876
+ # @!visibility private
877
+ def update_emoji_data(new_data)
878
+ @emoji = {}
879
+ process_emoji(new_data['emojis'])
880
+ end
881
+
882
+ # The inspect method is overwritten to give more useful output
883
+ def inspect
884
+ "<Server name=#{@name} id=#{@id} large=#{@large} region=#{@region} owner=#{@owner} afk_channel_id=#{@afk_channel_id} system_channel_id=#{@system_channel_id} afk_timeout=#{@afk_timeout}>"
885
+ end
886
+
887
+ private
888
+
889
+ def update_server_data(new_data)
890
+ response = JSON.parse(API::Server.update(@bot.token, @id,
891
+ new_data[:name] || @name,
892
+ new_data[:region] || @region_id,
893
+ new_data[:icon_id] || @icon_id,
894
+ new_data[:afk_channel_id] || @afk_channel_id,
895
+ new_data[:afk_timeout] || @afk_timeout,
896
+ new_data[:splash] || @splash,
897
+ new_data[:default_message_notifications] || @default_message_notifications,
898
+ new_data[:verification_level] || @verification_level,
899
+ new_data[:explicit_content_filter] || @explicit_content_filter,
900
+ new_data[:system_channel_id] || @system_channel_id))
901
+ update_data(response)
902
+ end
903
+
904
+ def process_roles(roles)
905
+ # Create roles
906
+ @roles = []
907
+ @roles_by_id = {}
908
+
909
+ return unless roles
910
+
911
+ roles.each do |element|
912
+ role = Role.new(element, @bot, self)
913
+ @roles << role
914
+ @roles_by_id[role.id] = role
915
+ end
916
+ end
917
+
918
+ def process_emoji(emoji)
919
+ return if emoji.empty?
920
+
921
+ emoji.each do |element|
922
+ new_emoji = Emoji.new(element, @bot, self)
923
+ @emoji[new_emoji.id] = new_emoji
924
+ end
925
+ end
926
+
927
+ def process_members(members)
928
+ return unless members
929
+
930
+ members.each do |element|
931
+ member = Member.new(element, self, @bot)
932
+ @members[member.id] = member
933
+ end
934
+ end
935
+
936
+ def process_presences(presences)
937
+ # Update user statuses with presence info
938
+ return unless presences
939
+
940
+ presences.each do |element|
941
+ next unless element['user']
942
+
943
+ user_id = element['user']['id'].to_i
944
+ user = @members[user_id]
945
+ if user
946
+ user.update_presence(element)
947
+ else
948
+ LOGGER.warn "Rogue presence update! #{element['user']['id']} on #{@id}"
949
+ end
950
+ end
951
+ end
952
+
953
+ def process_channels(channels)
954
+ @channels = []
955
+ @channels_by_id = {}
956
+
957
+ return unless channels
958
+
959
+ channels.each do |element|
960
+ channel = @bot.ensure_channel(element, self)
961
+ @channels << channel
962
+ @channels_by_id[channel.id] = channel
963
+ end
964
+ end
965
+
966
+ def process_voice_states(voice_states)
967
+ return unless voice_states
968
+
969
+ voice_states.each do |element|
970
+ update_voice_state(element)
971
+ end
972
+ end
973
+ end
974
+
975
+ # A ban entry on a server
976
+ class ServerBan
977
+ # @return [String, nil] the reason the user was banned, if provided
978
+ attr_reader :reason
979
+
980
+ # @return [User] the user that was banned
981
+ attr_reader :user
982
+
983
+ # @return [Server] the server this ban belongs to
984
+ attr_reader :server
985
+
986
+ # @!visibility private
987
+ def initialize(server, user, reason)
988
+ @server = server
989
+ @user = user
990
+ @reason = reason
991
+ end
992
+
993
+ # Removes this ban on the associated user in the server
994
+ # @param reason [String] the reason for removing the ban
995
+ def remove(reason = nil)
996
+ @server.unban(user, reason)
997
+ end
998
+
999
+ alias_method :unban, :remove
1000
+ alias_method :lift, :remove
1001
+ end
1002
+ end