rubycord 1.0.0

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