discordrb 3.3.0 → 3.5.0

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