onyxcord 1.1.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 (133) hide show
  1. checksums.yaml +7 -0
  2. data/.devcontainer/Dockerfile +13 -0
  3. data/.devcontainer/devcontainer.json +29 -0
  4. data/.devcontainer/postcreate.sh +4 -0
  5. data/.github/CONTRIBUTING.md +13 -0
  6. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  7. data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  8. data/.github/pull_request_template.md +37 -0
  9. data/.github/workflows/ci.yml +78 -0
  10. data/.github/workflows/codeql.yml +65 -0
  11. data/.github/workflows/deploy.yml +54 -0
  12. data/.github/workflows/release.yml +51 -0
  13. data/.gitignore +16 -0
  14. data/.markdownlint.json +4 -0
  15. data/.overcommit.yml +7 -0
  16. data/.rspec +2 -0
  17. data/.rubocop.yml +129 -0
  18. data/.yardopts +1 -0
  19. data/CHANGELOG.md +0 -0
  20. data/Gemfile +7 -0
  21. data/LICENSE.txt +21 -0
  22. data/README.md +305 -0
  23. data/Rakefile +17 -0
  24. data/bin/console +15 -0
  25. data/bin/setup +7 -0
  26. data/lib/onyxcord/allowed_mentions.rb +43 -0
  27. data/lib/onyxcord/api/application.rb +316 -0
  28. data/lib/onyxcord/api/channel.rb +700 -0
  29. data/lib/onyxcord/api/interaction.rb +67 -0
  30. data/lib/onyxcord/api/invite.rb +44 -0
  31. data/lib/onyxcord/api/server.rb +775 -0
  32. data/lib/onyxcord/api/user.rb +158 -0
  33. data/lib/onyxcord/api/webhook.rb +163 -0
  34. data/lib/onyxcord/api.rb +335 -0
  35. data/lib/onyxcord/await.rb +51 -0
  36. data/lib/onyxcord/bot.rb +1971 -0
  37. data/lib/onyxcord/cache.rb +326 -0
  38. data/lib/onyxcord/colour_rgb.rb +43 -0
  39. data/lib/onyxcord/commands/command_bot.rb +511 -0
  40. data/lib/onyxcord/commands/container.rb +112 -0
  41. data/lib/onyxcord/commands/events.rb +11 -0
  42. data/lib/onyxcord/commands/parser.rb +327 -0
  43. data/lib/onyxcord/commands/rate_limiter.rb +144 -0
  44. data/lib/onyxcord/configuration.rb +125 -0
  45. data/lib/onyxcord/container.rb +988 -0
  46. data/lib/onyxcord/data/activity.rb +271 -0
  47. data/lib/onyxcord/data/application.rb +341 -0
  48. data/lib/onyxcord/data/attachment.rb +91 -0
  49. data/lib/onyxcord/data/audit_logs.rb +438 -0
  50. data/lib/onyxcord/data/avatar_decoration.rb +26 -0
  51. data/lib/onyxcord/data/call.rb +22 -0
  52. data/lib/onyxcord/data/channel.rb +1355 -0
  53. data/lib/onyxcord/data/channel_tag.rb +69 -0
  54. data/lib/onyxcord/data/collectibles.rb +47 -0
  55. data/lib/onyxcord/data/component.rb +583 -0
  56. data/lib/onyxcord/data/embed.rb +258 -0
  57. data/lib/onyxcord/data/emoji.rb +123 -0
  58. data/lib/onyxcord/data/install_params.rb +24 -0
  59. data/lib/onyxcord/data/integration.rb +144 -0
  60. data/lib/onyxcord/data/interaction.rb +1141 -0
  61. data/lib/onyxcord/data/invite.rb +137 -0
  62. data/lib/onyxcord/data/member.rb +528 -0
  63. data/lib/onyxcord/data/message.rb +612 -0
  64. data/lib/onyxcord/data/message_activity.rb +41 -0
  65. data/lib/onyxcord/data/overwrite.rb +109 -0
  66. data/lib/onyxcord/data/poll.rb +365 -0
  67. data/lib/onyxcord/data/primary_server.rb +60 -0
  68. data/lib/onyxcord/data/profile.rb +79 -0
  69. data/lib/onyxcord/data/reaction.rb +64 -0
  70. data/lib/onyxcord/data/recipient.rb +34 -0
  71. data/lib/onyxcord/data/role.rb +449 -0
  72. data/lib/onyxcord/data/role_connection_data.rb +69 -0
  73. data/lib/onyxcord/data/role_subscription.rb +41 -0
  74. data/lib/onyxcord/data/scheduled_event.rb +513 -0
  75. data/lib/onyxcord/data/server.rb +1614 -0
  76. data/lib/onyxcord/data/server_preview.rb +68 -0
  77. data/lib/onyxcord/data/snapshot.rb +112 -0
  78. data/lib/onyxcord/data/team.rb +98 -0
  79. data/lib/onyxcord/data/timestamp.rb +69 -0
  80. data/lib/onyxcord/data/user.rb +324 -0
  81. data/lib/onyxcord/data/voice_region.rb +46 -0
  82. data/lib/onyxcord/data/voice_state.rb +41 -0
  83. data/lib/onyxcord/data/webhook.rb +238 -0
  84. data/lib/onyxcord/data.rb +57 -0
  85. data/lib/onyxcord/errors.rb +246 -0
  86. data/lib/onyxcord/event_executor.rb +80 -0
  87. data/lib/onyxcord/events/await.rb +48 -0
  88. data/lib/onyxcord/events/bans.rb +60 -0
  89. data/lib/onyxcord/events/channels.rb +225 -0
  90. data/lib/onyxcord/events/generic.rb +129 -0
  91. data/lib/onyxcord/events/guilds.rb +269 -0
  92. data/lib/onyxcord/events/integrations.rb +100 -0
  93. data/lib/onyxcord/events/interactions.rb +624 -0
  94. data/lib/onyxcord/events/invites.rb +127 -0
  95. data/lib/onyxcord/events/lifetime.rb +31 -0
  96. data/lib/onyxcord/events/members.rb +110 -0
  97. data/lib/onyxcord/events/message.rb +399 -0
  98. data/lib/onyxcord/events/polls.rb +118 -0
  99. data/lib/onyxcord/events/presence.rb +131 -0
  100. data/lib/onyxcord/events/raw.rb +74 -0
  101. data/lib/onyxcord/events/reactions.rb +218 -0
  102. data/lib/onyxcord/events/roles.rb +87 -0
  103. data/lib/onyxcord/events/scheduled_events.rb +171 -0
  104. data/lib/onyxcord/events/threads.rb +100 -0
  105. data/lib/onyxcord/events/typing.rb +73 -0
  106. data/lib/onyxcord/events/voice_server_update.rb +48 -0
  107. data/lib/onyxcord/events/voice_state_update.rb +106 -0
  108. data/lib/onyxcord/events/webhooks.rb +65 -0
  109. data/lib/onyxcord/gateway.rb +890 -0
  110. data/lib/onyxcord/id_object.rb +39 -0
  111. data/lib/onyxcord/light/data.rb +62 -0
  112. data/lib/onyxcord/light/integrations.rb +73 -0
  113. data/lib/onyxcord/light/light_bot.rb +58 -0
  114. data/lib/onyxcord/light.rb +8 -0
  115. data/lib/onyxcord/logger.rb +120 -0
  116. data/lib/onyxcord/message_components.rb +70 -0
  117. data/lib/onyxcord/paginator.rb +60 -0
  118. data/lib/onyxcord/permissions.rb +255 -0
  119. data/lib/onyxcord/rate_limiter/gateway.rb +42 -0
  120. data/lib/onyxcord/rate_limiter/rest.rb +89 -0
  121. data/lib/onyxcord/version.rb +7 -0
  122. data/lib/onyxcord/voice/encoder.rb +115 -0
  123. data/lib/onyxcord/voice/network.rb +380 -0
  124. data/lib/onyxcord/voice/opcodes.rb +29 -0
  125. data/lib/onyxcord/voice/sodium.rb +157 -0
  126. data/lib/onyxcord/voice/timer.rb +19 -0
  127. data/lib/onyxcord/voice/voice_bot.rb +386 -0
  128. data/lib/onyxcord/webhooks.rb +14 -0
  129. data/lib/onyxcord/websocket.rb +62 -0
  130. data/lib/onyxcord.rb +180 -0
  131. data/onyxcord-webhooks.gemspec +30 -0
  132. data/onyxcord.gemspec +50 -0
  133. metadata +421 -0
@@ -0,0 +1,1614 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnyxCord
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 method to get a server's icon URL.
13
+ # @param format [String] The URL will default to `webp`. You can otherwise specify one of `jpg`
14
+ # or `png` to override this.
15
+ # @return [String, nil] The URL to the server's icon, or `nil` if the server hasn't set an icon.
16
+ def icon_url(format: 'webp')
17
+ API.icon_url(@id, @icon_id, format) if @icon_id
18
+ end
19
+ end
20
+
21
+ # An isolated collection of channels and member's 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 [Hash<Integer => Emoji>] a hash of all the emoji available on this server.
33
+ attr_reader :emoji
34
+ alias_method :emojis, :emoji
35
+
36
+ # @return [true, false] whether or not this server is large (members > 100). If it is,
37
+ # it means the members list may be inaccurate for a couple seconds after starting up the bot.
38
+ attr_reader :large
39
+ alias_method :large?, :large
40
+
41
+ # @return [Array<Symbol>] the features of the server (eg. "INVITE_SPLASH")
42
+ attr_reader :features
43
+
44
+ # @return [Integer] the absolute number of members on this server, offline or not.
45
+ attr_reader :member_count
46
+
47
+ # @return [Integer] the amount of time after which a voice user gets moved into the AFK channel, in seconds.
48
+ attr_reader :afk_timeout
49
+
50
+ # @return [Hash<Integer => VoiceState>] the hash (user ID => voice state) of voice states of members on this server
51
+ attr_reader :voice_states
52
+
53
+ # @return [Integer] the server's amount of Nitro boosters, 0 if no one has boosted.
54
+ attr_reader :booster_count
55
+
56
+ # @return [Integer] the server's Nitro boost level, 0 if no level.
57
+ attr_reader :boost_level
58
+
59
+ # @return [String] the preferred locale of the server. Used in server discovery and notices from Discord.
60
+ attr_reader :locale
61
+
62
+ # @return [String, nil] the description of the server. Shown in server discovery and external embeds.
63
+ attr_reader :description
64
+
65
+ # @return [String, nil] the hash of the server's banner image or GIF.
66
+ attr_reader :banner_id
67
+
68
+ # @return [String, nil] the hash of the server's invite splash image.
69
+ attr_reader :splash_id
70
+ alias_method :splash_hash, :splash_id
71
+
72
+ # @return [Integer] the maximum number of members that can join the server.
73
+ attr_reader :max_member_count
74
+
75
+ # @return [String, nil] the code of the server's custom vanity invite link.
76
+ attr_reader :vanity_invite_code
77
+
78
+ # @return [Integer, nil] the maximum number of members that can concurrently be online in the server.
79
+ # Always set to `nil` except for the largest of servers.
80
+ attr_reader :max_presence_count
81
+
82
+ # @return [String, nil] the hash of the server's discovery splash image.
83
+ attr_reader :discovery_splash_id
84
+
85
+ # @return [Integer] the flags for the server's designated system channel.
86
+ attr_reader :system_channel_flags
87
+
88
+ # @return [Integer] the maximum number of members that can concurrently watch a stream in a video channel.
89
+ attr_reader :max_video_channel_members
90
+
91
+ # @return [Integer] the maximum number of members that can concurrently watch a stream in a stage channel.
92
+ attr_reader :max_stage_video_channel_members
93
+
94
+ # @return [true, false] whether or not the server has the boost progress bar enabled.
95
+ attr_reader :boost_progress_bar
96
+ alias_method :boost_progress_bar?, :boost_progress_bar
97
+
98
+ # @return [Time, nil] the time at when the last raid was detected on the server.
99
+ attr_reader :raid_detected_at
100
+
101
+ # @return [Time, nil] the time at when DM spam was last detected on the server.
102
+ attr_reader :dm_spam_detected_at
103
+
104
+ # @return [Time, nil] the time at when invites will be re-enabled on the server.
105
+ attr_reader :invites_disabled_until
106
+
107
+ # @return [Time, nil] the time at when non-friend direct messages will be re-enabled on the server.
108
+ attr_reader :dms_disabled_until
109
+
110
+ # @!visibility private
111
+ def initialize(data, bot)
112
+ @bot = bot
113
+ @id = data['id'].to_i
114
+ @members = {}
115
+ @voice_states = {}
116
+ @emoji = {}
117
+ @channels = []
118
+ @channels_by_id = {}
119
+ @scheduled_events = {}
120
+
121
+ update_data(data)
122
+
123
+ # Whether this server's members have been chunked (resolved using op 8 and GUILD_MEMBERS_CHUNK) yet
124
+ @chunked = false
125
+ end
126
+
127
+ # @return [Member] The server owner.
128
+ def owner
129
+ member(@owner_id)
130
+ end
131
+
132
+ # The default channel is the text channel on this server with the highest position
133
+ # that the bot has Read Messages permission on.
134
+ # @param send_messages [true, false] whether to additionally consider if the bot has Send Messages permission
135
+ # @return [Channel, nil] The default channel on this server, or `nil` if there are no channels that the bot can read.
136
+ def default_channel(send_messages = false)
137
+ bot_member = member(@bot.profile)
138
+ text_channels.sort_by { |e| [e.position, e.id] }.find do |e|
139
+ if send_messages
140
+ bot_member.can_read_messages?(e) && bot_member.can_send_messages?(e)
141
+ else
142
+ bot_member.can_read_messages?(e)
143
+ end
144
+ end
145
+ end
146
+
147
+ alias_method :general_channel, :default_channel
148
+
149
+ # @return [Role] The @everyone role on this server
150
+ def everyone_role
151
+ @roles[@id]
152
+ end
153
+
154
+ # @return [Array<Role>] an array of all the roles available on this server.
155
+ def roles
156
+ @roles.values
157
+ end
158
+
159
+ # Gets a role on this server based on its ID.
160
+ # @param id [String, Integer] The role ID to look for.
161
+ # @return [Role, nil] The role identified by the ID, or `nil` if it couldn't be found.
162
+ def role(id)
163
+ @roles[id.resolve_id]
164
+ end
165
+
166
+ # Get a mapping of role IDs to the amount of members who have the role.
167
+ # @example Print out the name of the roles in a server followed by the role's member count.
168
+ # server = bot.server(81384788765712384)
169
+ #
170
+ # server.role_member_counts.each do |id, count|
171
+ # puts("Name: #{server.role(id).name}, Count: #{count}")
172
+ # end
173
+ # @return [Hash<Integer => Integer>] A hash mapping role IDs to their respective member counts.
174
+ def role_member_counts
175
+ response = JSON.parse(API::Server.role_member_counts(@bot.token, @id))
176
+ response.transform_keys!(&:to_i)
177
+ response.tap { |hash| hash[@id] = @member_count }
178
+ end
179
+
180
+ # Gets a member on this server based on user ID
181
+ # @param id [Integer] The user ID to look for
182
+ # @param request [true, false] Whether the member should be requested from Discord if it's not cached
183
+ def member(id, request = true)
184
+ id = id.resolve_id
185
+ return @members[id] if member_cached?(id)
186
+ return nil unless request
187
+
188
+ member = @bot.member(self, id)
189
+ @members[id] = member unless member.nil?
190
+ rescue StandardError
191
+ nil
192
+ end
193
+
194
+ # @return [Array<Member>] an array of all the members on this server.
195
+ # @raise [RuntimeError] if the bot was not started with the :server_member intent
196
+ def members
197
+ return @members.values if @chunked
198
+
199
+ @bot.debug("Members for server #{@id} not chunked yet - initiating")
200
+
201
+ # If the SERVER_MEMBERS intent flag isn't set, the gateway won't respond when we ask for members.
202
+ raise 'The :server_members intent is required to get server members' if @bot.gateway.intents.nobits?(INTENTS[:server_members])
203
+
204
+ @bot.request_chunks(@id)
205
+ sleep 0.05 until @chunked
206
+ @members.values
207
+ end
208
+
209
+ alias_method :users, :members
210
+
211
+ # @return [Array<Member>] an array of all the bot members on this server.
212
+ def bot_members
213
+ members.select(&:bot_account?)
214
+ end
215
+
216
+ # @return [Array<Member>] an array of all the non bot members on this server.
217
+ def non_bot_members
218
+ members.reject(&:bot_account?)
219
+ end
220
+
221
+ # @return [Member] the bot's own `Member` on this server
222
+ def bot
223
+ member(@bot.profile)
224
+ end
225
+
226
+ # @return [Array<Integration>] an array of the integrations in this server.
227
+ # @note If the server has more than 50 integrations, they cannot be accessed.
228
+ def integrations
229
+ integration = JSON.parse(API::Server.integrations(@bot.token, @id))
230
+ integration.map { |element| Integration.new(element, @bot, self) }
231
+ end
232
+
233
+ # @param action [Symbol] The action to only include.
234
+ # @param user [User, String, Integer] The user, or their ID, to filter entries to.
235
+ # @param limit [Integer] The amount of entries to limit it to.
236
+ # @param before [Entry, String, Integer] The entry, or its ID, to use to not include all entries after it.
237
+ # @return [AuditLogs] The server's audit logs.
238
+ def audit_logs(action: nil, user: nil, limit: 50, before: nil)
239
+ raise 'Invalid audit log action!' if action && AuditLogs::ACTIONS.key(action).nil?
240
+
241
+ action = AuditLogs::ACTIONS.key(action)
242
+ user = user.resolve_id if user
243
+ before = before.resolve_id if before
244
+ AuditLogs.new(self, @bot, JSON.parse(API::Server.audit_logs(@bot.token, @id, limit, user, action, before)))
245
+ end
246
+
247
+ # @note For internal use only
248
+ # @!visibility private
249
+ def cache_widget_data(data = nil)
250
+ data ||= if bot.permission?(:manage_server)
251
+ JSON.parse(API::Server.widget(@bot.token, @id))
252
+ else
253
+ return update_data(nil)
254
+ end
255
+
256
+ @widget_enabled = data['enabled']
257
+ @widget_channel_id = data['channel_id']
258
+ end
259
+
260
+ # @return [true, false] whether or not the server has widget enabled
261
+ def widget_enabled?
262
+ cache_widget_data if @widget_enabled.nil?
263
+ @widget_enabled
264
+ end
265
+ alias_method :widget?, :widget_enabled?
266
+ alias_method :embed_enabled, :widget_enabled?
267
+ alias_method :embed?, :widget_enabled?
268
+
269
+ # @return [Channel, nil] the channel the server widget will make an invite for.
270
+ def widget_channel
271
+ cache_widget_data if @widget_enabled.nil?
272
+ @bot.channel(@widget_channel_id) if @widget_channel_id
273
+ end
274
+ alias_method :embed_channel, :widget_channel
275
+
276
+ # Sets whether this server's widget is enabled
277
+ # @param value [true, false]
278
+ # @deprecated Please migrate to using {#modify} with the `widget_enabled:` parameter.
279
+ def widget_enabled=(value)
280
+ modify_widget(value, widget_channel)
281
+ end
282
+ alias_method :embed_enabled=, :widget_enabled=
283
+
284
+ # Sets whether this server's widget is enabled
285
+ # @param value [true, false]
286
+ # @param reason [String, nil] the reason to be shown in the audit log for this action
287
+ # @deprecated Please migrate to using {#modify} with the `widget_enabled:` parameter.
288
+ def set_widget_enabled(value, reason = nil)
289
+ modify_widget(value, widget_channel, reason)
290
+ end
291
+ alias_method :set_embed_enabled, :set_widget_enabled
292
+
293
+ # Changes the channel on the server's widget
294
+ # @param channel [Channel, String, Integer] the channel, or its ID, to be referenced by the widget
295
+ # @deprecated Please migrate to using {#modify} with the `widget_channel:` parameter.
296
+ def widget_channel=(channel)
297
+ modify_widget(widget?, channel)
298
+ end
299
+ alias_method :embed_channel=, :widget_channel=
300
+
301
+ # Changes the channel on the server's widget
302
+ # @param channel [Channel, String, Integer] the channel, or its ID, to be referenced by the widget
303
+ # @param reason [String, nil] the reason to be shown in the audit log for this action
304
+ # @deprecated Please migrate to using {#modify} with the `widget_channel:` parameter.
305
+ def set_widget_channel(channel, reason = nil)
306
+ modify_widget(widget?, channel, reason)
307
+ end
308
+ alias_method :set_embed_channel, :set_widget_channel
309
+
310
+ # Changes the channel on the server's widget, and sets whether it is enabled.
311
+ # @param enabled [true, false] whether the widget is enabled
312
+ # @param channel [Channel, String, Integer] the channel, or its ID, to be referenced by the widget
313
+ # @param reason [String, nil] the reason to be shown in the audit log for this action
314
+ # @deprecated Please migrate to using {#modify} with the `widget_enabled:` and `widget_channel:` parameters.
315
+ def modify_widget(enabled, channel, reason = nil)
316
+ cache_widget_data if @widget_enabled.nil?
317
+ channel_id = channel ? channel.resolve_id : @widget_channel_id
318
+ cache_widget_data(JSON.parse(API::Server.modify_widget(@bot.token, @id, enabled, channel_id, reason)))
319
+ end
320
+ alias_method :modify_embed, :modify_widget
321
+
322
+ # @param include_idle [true, false] Whether to count idle members as online.
323
+ # @param include_bots [true, false] Whether to include bot accounts in the count.
324
+ # @return [Array<Member>] an array of online members on this server.
325
+ def online_members(include_idle: false, include_bots: true)
326
+ @members.values.select do |e|
327
+ ((include_idle ? e.idle? : false) || e.online?) && (include_bots ? true : !e.bot_account?)
328
+ end
329
+ end
330
+
331
+ alias_method :online_users, :online_members
332
+
333
+ # Adds a member to this guild that has granted this bot's application an OAuth2 access token
334
+ # with the `guilds.join` scope.
335
+ # For more information about Discord's OAuth2 implementation, see: https://discord.com/developers/docs/topics/oauth2
336
+ # @note Your bot must be present in this server, and have permission to create instant invites for this to work.
337
+ # @param user [User, String, Integer] the user, or ID of the user to add to this server
338
+ # @param access_token [String] the OAuth2 Bearer token that has been granted the `guilds.join` scope
339
+ # @param nick [String] the nickname to give this member upon joining
340
+ # @param roles [Role, Array<Role, String, Integer>] the role (or roles) to give this member upon joining
341
+ # @param deaf [true, false] whether this member will be server deafened upon joining
342
+ # @param mute [true, false] whether this member will be server muted upon joining
343
+ # @return [Member, nil] the created member, or `nil` if the user is already a member of this server.
344
+ def add_member_using_token(user, access_token, nick: nil, roles: [], deaf: false, mute: false)
345
+ user_id = user.resolve_id
346
+ roles = roles.is_a?(Array) ? roles.map(&:resolve_id) : [roles.resolve_id]
347
+ response = API::Server.add_member(@bot.token, @id, user_id, access_token, nick, roles, deaf, mute)
348
+ return nil if response.empty?
349
+
350
+ add_member Member.new(JSON.parse(response), self, @bot)
351
+ end
352
+
353
+ # Returns the amount of members that are candidates for pruning
354
+ # @param days [Integer] the number of days to consider for inactivity
355
+ # @return [Integer] number of members to be removed
356
+ # @raise [ArgumentError] if days is not between 1 and 30 (inclusive)
357
+ def prune_count(days)
358
+ raise ArgumentError, 'Days must be between 1 and 30' unless days.between?(1, 30)
359
+
360
+ response = JSON.parse API::Server.prune_count(@bot.token, @id, days)
361
+ response['pruned']
362
+ end
363
+
364
+ # Prunes (kicks) an amount of members for inactivity
365
+ # @param days [Integer] the number of days to consider for inactivity (between 1 and 30)
366
+ # @param reason [String] The reason the for the prune.
367
+ # @return [Integer] the number of members removed at the end of the operation
368
+ # @raise [ArgumentError] if days is not between 1 and 30 (inclusive)
369
+ def begin_prune(days, reason = nil)
370
+ raise ArgumentError, 'Days must be between 1 and 30' unless days.between?(1, 30)
371
+
372
+ response = JSON.parse API::Server.begin_prune(@bot.token, @id, days, reason)
373
+ response['pruned']
374
+ end
375
+
376
+ alias_method :prune, :begin_prune
377
+
378
+ # @return [Array<Channel>] an array of text channels on this server
379
+ def text_channels
380
+ @channels.select(&:text?)
381
+ end
382
+
383
+ # @return [Array<Channel>] an array of voice channels on this server
384
+ def voice_channels
385
+ @channels.select(&:voice?)
386
+ end
387
+
388
+ # @return [Array<Channel>] an array of category channels on this server
389
+ def categories
390
+ @channels.select(&:category?)
391
+ end
392
+
393
+ # @return [Array<Channel>] an array of channels on this server that are not in a category
394
+ def orphan_channels
395
+ @channels.reject { |c| c.parent || c.category? }
396
+ end
397
+
398
+ # @return [ServerPreview] the preview of this server shown in the discovery page.
399
+ def preview
400
+ @bot.server_preview(@id)
401
+ end
402
+
403
+ # @return [String, nil] the widget URL to the server that displays the amount of online members in a
404
+ # stylish way. `nil` if the widget is not enabled.
405
+ def widget_url
406
+ update_data if @widget_enabled.nil?
407
+
408
+ API.widget_url(@id) if @widget_enabled
409
+ end
410
+
411
+ # @param style [Symbol] The style the picture should have. Possible styles are:
412
+ # * `: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.
413
+ # * `: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.
414
+ # * `: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.
415
+ # * `: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.
416
+ # * `: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.
417
+ # @return [String, nil] the widget banner URL to the server that displays the amount of online members,
418
+ # server icon and server name in a stylish way. `nil` if the widget is not enabled.
419
+ def widget_banner_url(style)
420
+ update_data if @widget_enabled.nil?
421
+
422
+ API.widget_url(@id, style) if @widget_enabled
423
+ end
424
+
425
+ # Utility method to get a server's splash URL.
426
+ # @param format [String] The URL will default to `webp`. You can otherwise specify one of `jpg` or `png` to
427
+ # override this.
428
+ # @return [String, nil] The URL to the server's splash image, or `nil` if the server doesn't have a splash image.
429
+ def splash_url(format: 'webp')
430
+ API.splash_url(@id, @splash_id, format) if @splash_id
431
+ end
432
+
433
+ # Utility method to get a server's banner URL.
434
+ # @param format [String] The URL will default to `webp`. You can otherwise specify one of `jpg` or `png` to
435
+ # override this.
436
+ # @return [String, nil] The URL to the server's banner image, or `nil` if the server doesn't have a banner image.
437
+ def banner_url(format: 'webp')
438
+ API.banner_url(@id, @banner_id, format) if @banner_id
439
+ end
440
+
441
+ # Utility method to get a server's discovery splash URL.
442
+ # @param format [String] The URL will default to `webp`. You can otherwise specify one of `jpg` or `png` to override this.
443
+ # @return [String, nil] The URL to the server's discovery splash image, or `nil` if the server doesn't have a discovery splash image.
444
+ def discovery_splash_url(format: 'webp')
445
+ API.discovery_splash_url(@id, @discovery_splash_id, format) if @discovery_splash_id
446
+ end
447
+
448
+ # @return [String] a URL that a user can use to navigate to this server in the client
449
+ def link
450
+ "https://discord.com/channels/#{@id}"
451
+ end
452
+
453
+ alias_method :jump_link, :link
454
+
455
+ # Search the messages that have been sent in this server.
456
+ # @example Search for 200 messages from a user that contain an attachment.
457
+ # options = {
458
+ # limit: 200,
459
+ # contains: :file,
460
+ # authors: 171764626755813376
461
+ # }
462
+ #
463
+ # results = server.search_messages(**options)
464
+ # @example Search for all of the messages in a channel that mentions someone.
465
+ # options = {
466
+ # limit: nil,
467
+ # mentions: 171764626755813376,
468
+ # channels: 381891448884428801
469
+ # }
470
+ #
471
+ # results = server.search_messages(**options)
472
+ # @example Search for 105 messages that contain specific embed types, sorted by oldest to newest.
473
+ # options = {
474
+ # limit: 105,
475
+ # embed_types: %i[article image],
476
+ # sort_order: :ascending
477
+ # }
478
+ #
479
+ # results = server.search_messages(**options)
480
+ # @example Search for 30 messages sent between two dates that contain the word “time” and an @everyone ping.
481
+ # options = {
482
+ # limit: 30,
483
+ # content: 'time',
484
+ # mentions_everyone: true,
485
+ # after: Time.parse("December 16th, 2020"),
486
+ # before: Time.parse("December 25th, 2020")
487
+ # }
488
+ #
489
+ # results = server.search_messages(**options)
490
+ # @example Search for 500 messages that reply to a specific message, contain a Ruby file, and were sent by a bot account.
491
+ # options = {
492
+ # limit: 500,
493
+ # author_types: :bot,
494
+ # file_extensions: '.rb',
495
+ # reply_messages: 1454184993923268660
496
+ # }
497
+ #
498
+ # results = server.search_messages(**options)
499
+ # @param limit [Integer, nil] The maximum number of messages to return, or `nil` to fetch all of the messages that match the search query.
500
+ # @param offset [Integer, nil] The number of messages between 0-9975 to offset the search query by.
501
+ # @param before [Time, #resolve_id, nil] Get messages sent before this timestamp.
502
+ # @param after [Time, #resolve_id, nil] Get messages sent after this timestamp.
503
+ # @param content [String, #to_s, nil] Get messages with matching message content.
504
+ # @param slop [Integer, nil] The amount of variation allowed between the placement of words when matching against message content; between 0-100.
505
+ # @param channels [Array<Channel, Integer, String>, Channel, Integer, String, nil] Get messages that were sent in these channels.
506
+ # @param authors [Array<#resolve_id>, #resolve_id, nil] Get messages that were created by these authors.
507
+ # @param author_types [Array<String, Symbol>, String, Symbol, nil] Get messages that were created by these author types: `user`, `bot`, or `webhook`.
508
+ # @param mentions [Array<#resolve_id>, #resolve_id, nil] Get messages that mention these users or members.
509
+ # @param role_mentions [Array<Role, Integer, String>, Role, Integer, String, nil] Get messages that mention these roles.
510
+ # @param mentions_everyone [true, false, nil] Get messages that mention the @everyone role.
511
+ # @param reply_users [Array<#resolve_id>, #resolve_id, nil] Get messages that replied to these users or members.
512
+ # @param reply_messages [Array<Message, Integer, String>, Message, Integer, String, nil] Get messages that replied to these messages.
513
+ # @param pinned [true, false, nil] Get messages that are pinned.
514
+ # @param contains [Array<String, Symbol>, String, Symbol, nil] Get messages that contain specific fields, e.g. `file`, `poll`, `sound`, etc.
515
+ # @param embed_types [Array<String, Symbol>, String, Symbol, nil] Get messages that contain matching embed types.
516
+ # @param embed_providers [Array<String, Symbol>, String, Symbol, nil] Get messages that contain embeds from specific providers.
517
+ # @param link_hosts [Array<String, Symbol>, String, Symbol, nil] Get messages that contain matching link hostnames, e.g. `discord.com`.
518
+ # @param file_names [Array<String, Symbol, Attachment>, String, Symbol, Attachment, nil] Get messages that contain matching attachment filenames.
519
+ # @param file_extensions [Array<String, Symbol>, String, Symbol, nil] Get messages that contain matching attachment file extensions, e.g. `.rb`, `.mp3`, etc.
520
+ # @param include_nsfw [true, false, nil] Whether or not to include messages that have been sent in NSFW channels.
521
+ # @param sort_by [Symbol, String, nil] Whether to sort the returned messages by their `:creation_time`, or `:relevance` to the search query.
522
+ # @param sort_order [Symbol, string, nil] Whether to order the returned messages in `:descending`, or `:ascending` order. Not respected when sorting by `:relevance`.
523
+ # @raise [OnyxCord::Errors::NoPermission] This may occur when the application has not enabled the `MESSAGE_CONTENT` privileged intent on the Discord Developer Portal.
524
+ # @note Messages with GIFs sent before February 24th, 2026 may not be returned under the `gif` embed type when using the `embed_types:` parameter.
525
+ # @note Messages fetched via this method will not contain reactions. This means that {Message#reactions} will **always** return an empty array, even if the message has reactions.
526
+ # @return [SearchedMessages] the results of the search query.
527
+ def search_messages(
528
+ limit: 25, offset: nil, before: nil, after: nil, content: nil, slop: 2, channels: nil, authors: nil, author_types: nil,
529
+ mentions: nil, role_mentions: nil, mentions_everyone: nil, reply_users: nil, reply_messages: nil, pinned: nil, contains: nil,
530
+ embed_types: nil, embed_providers: nil, link_hosts: nil, file_names: nil, file_extensions: nil, include_nsfw: true, sort_by: nil,
531
+ sort_order: :descending
532
+ )
533
+ sort_order = case sort_order&.to_sym
534
+ when nil, :desc, :descending, :newest_first
535
+ :desc
536
+ when :asc, :ascending, :oldest_first
537
+ :asc
538
+ else
539
+ raise ArgumentError, "Invalid value for the 'sort_order' parameter"
540
+ end
541
+
542
+ sort_by = case sort_by&.to_sym
543
+ when nil, :timestamp, :creation_time
544
+ :timestamp
545
+ when :relevance, :match_score
546
+ :relevance
547
+ else
548
+ raise ArgumentError, "Invalid value for the 'sort_by' parameter"
549
+ end
550
+
551
+ options = {
552
+ limit: limit && limit <= 25 ? limit : 25,
553
+ max_id: before.is_a?(Time) ? IDObject.synthesise(before) : before&.resolve_id,
554
+ min_id: after.is_a?(Time) ? IDObject.synthesise(after) : after&.resolve_id,
555
+ offset: offset || 0,
556
+ slop: slop,
557
+ content: content&.to_s,
558
+ channel_id: channels ? Array(channels).map(&:resolve_id) : channels,
559
+ author_type: author_types ? Array(author_types) : author_types,
560
+ author_id: authors ? Array(authors).map(&:resolve_id) : authors,
561
+ mentions: mentions ? Array(mentions).map(&:resolve_id) : mentions,
562
+ mentions_role_id: role_mentions ? Array(role_mentions).map(&:resolve_id) : role_mentions,
563
+ mention_everyone: mentions_everyone,
564
+ replied_to_user_id: reply_users ? Array(reply_users).map(&:resolve_id) : reply_users,
565
+ replied_to_message_id: reply_messages ? Array(reply_messages).map(&:resolve_id) : reply_messages,
566
+ pinned: pinned,
567
+ has: contains ? Array(contains) : contains,
568
+ embed_type: embed_types ? Array(embed_types) : embed_types,
569
+ embed_provider: embed_providers ? Array(embed_providers) : embed_providers,
570
+ link_hostname: link_hosts ? Array(link_hosts) : link_hosts,
571
+ attachment_filename: (Array(file_names).map { |file| file.is_a?(Attachment) ? file.filename : file } if file_names),
572
+ attachment_extension: file_extensions ? Array(file_extensions).map { |type| type.to_s.delete_prefix('.') } : file_extensions,
573
+ sort_by: sort_by,
574
+ sort_order: sort_order,
575
+ include_nsfw: include_nsfw
576
+ }.compact
577
+
578
+ raise ArgumentError, "The 'role_mentions' parameter cannot contain the everyone role" if options[:mentions_role_id]&.any?(@id)
579
+
580
+ # Only store the total message count from the first request.
581
+ total = nil
582
+
583
+ get_messages = lambda do |query|
584
+ data = JSON.parse(API::Server.search_messages(@bot.token, @id, **options, **query.compact))
585
+ total ||= data['total_results']
586
+
587
+ data['threads']&.each do |thread|
588
+ thread['member'] = data['members']&.find { |member| thread['id'] == member['id'] }
589
+
590
+ @bot.ensure_channel(thread, self)
591
+ end
592
+
593
+ data['messages'].collect { |nested_messages| Message.new(nested_messages[0], @bot) }
594
+ end
595
+
596
+ paginator = Paginator.new(limit, :down) do |page|
597
+ if sort_by == :relevance
598
+ if (count = (paginator.amount_fetched + options[:offset])) > 9975
599
+ []
600
+ else
601
+ get_messages.call(offset: count)
602
+ end
603
+ elsif sort_order == :desc
604
+ get_messages.call(max_id: page&.last&.id, offset: page ? 0 : nil)
605
+ else
606
+ get_messages.call(min_id: page&.last&.id, offset: page ? 0 : nil)
607
+ end
608
+ end
609
+
610
+ SearchedMessages.new(paginator.to_a, total, @bot)
611
+ end
612
+
613
+ # Adds a role to the role cache
614
+ # @note For internal use only
615
+ # @!visibility private
616
+ def add_role(role)
617
+ @roles[role.id] = role
618
+ end
619
+
620
+ # Removes a role from the role cache
621
+ # @note For internal use only
622
+ # @!visibility private
623
+ def delete_role(role_id)
624
+ @roles.delete(role_id.resolve_id)
625
+ @members.each_value do |member|
626
+ new_roles = member.roles.reject { |r| r.id == role_id }
627
+ member.update_roles(new_roles)
628
+ end
629
+ @channels.each do |channel|
630
+ overwrites = channel.permission_overwrites.reject { |id, _| id == role_id }
631
+ channel.update_overwrites(overwrites)
632
+ end
633
+ end
634
+
635
+ # Updates the positions of all roles on the server
636
+ # @note For internal use only
637
+ # @!visibility private
638
+ def update_role_positions(role_positions, reason: nil)
639
+ response = JSON.parse(API::Server.update_role_positions(@bot.token, @id, role_positions, reason))
640
+ response.each { |data| role(data['id'].to_i)&.update_data(data) }
641
+ end
642
+
643
+ # Adds a member to the member cache.
644
+ # @note For internal use only
645
+ # @!visibility private
646
+ def add_member(member)
647
+ @member_count += 1
648
+ @members[member.id] = member
649
+ end
650
+
651
+ # Removes a member from the member cache.
652
+ # @note For internal use only
653
+ # @!visibility private
654
+ def delete_member(user_id)
655
+ @members.delete(user_id)
656
+ @member_count -= 1 unless @member_count <= 0
657
+ end
658
+
659
+ # Checks whether a member is cached
660
+ # @note For internal use only
661
+ # @!visibility private
662
+ def member_cached?(user_id)
663
+ @members.include?(user_id)
664
+ end
665
+
666
+ # Adds a member to the cache
667
+ # @note For internal use only
668
+ # @!visibility private
669
+ def cache_member(member)
670
+ @members[member.id] = member
671
+ end
672
+
673
+ # Adds a scheduled event to the cache
674
+ # @note For internal use only
675
+ # @!visibility private
676
+ def cache_scheduled_event(event)
677
+ @scheduled_events[event.id] = event
678
+ end
679
+
680
+ # Removes a scheduled event from the cache.
681
+ # @note For internal use only
682
+ # @!visibility private
683
+ def delete_scheduled_event(event)
684
+ @scheduled_events.delete(event.resolve_id)
685
+ end
686
+
687
+ # Updates a member's voice state
688
+ # @note For internal use only
689
+ # @!visibility private
690
+ def update_voice_state(data)
691
+ user_id = data['user_id'].to_i
692
+
693
+ if data['channel_id']
694
+ unless @voice_states[user_id]
695
+ # Create a new voice state for the user
696
+ @voice_states[user_id] = VoiceState.new(user_id)
697
+ end
698
+
699
+ # Update the existing voice state (or the one we just created)
700
+ channel = @channels_by_id[data['channel_id'].to_i]
701
+ @voice_states[user_id].update(
702
+ channel,
703
+ data['mute'],
704
+ data['deaf'],
705
+ data['self_mute'],
706
+ data['self_deaf']
707
+ )
708
+ else
709
+ # The user is not in a voice channel anymore, so delete its voice state
710
+ @voice_states.delete(user_id)
711
+ end
712
+ end
713
+
714
+ # Creates a channel on this server with the given name.
715
+ # @note If parent is provided, permission overwrites have the follow behavior:
716
+ #
717
+ # 1. If overwrites is null, the new channel inherits the parent's permissions.
718
+ # 2. If overwrites is [], the new channel inherits the parent's permissions.
719
+ # 3. If you supply one or more overwrites, the channel will be created with those permissions and ignore the parents.
720
+ #
721
+ # @param name [String] Name of the channel to create
722
+ # @param type [Integer, Symbol] Type of channel to create (0: text, 2: voice, 4: category, 5: news, 6: store)
723
+ # @param topic [String] the topic of this channel, if it will be a text channel
724
+ # @param bitrate [Integer] the bitrate of this channel, if it will be a voice channel
725
+ # @param user_limit [Integer] the user limit of this channel, if it will be a voice channel
726
+ # @param permission_overwrites [Array<Hash>, Array<Overwrite>] permission overwrites for this channel
727
+ # @param parent [Channel, String, Integer] parent category, or its ID, for this channel to be created in.
728
+ # @param nsfw [true, false] whether this channel should be created as nsfw
729
+ # @param rate_limit_per_user [Integer] how many seconds users need to wait in between messages.
730
+ # @param reason [String] The reason the for the creation of this channel.
731
+ # @return [Channel] the created channel.
732
+ # @raise [ArgumentError] if type is not 0 (text), 2 (voice), 4 (category), 5 (news), or 6 (store)
733
+ 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)
734
+ type = Channel::TYPES[type] if type.is_a?(Symbol)
735
+ 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)
736
+
737
+ permission_overwrites.map! { |e| e.is_a?(Overwrite) ? e.to_hash : e } if permission_overwrites.is_a?(Array)
738
+ parent_id = parent.respond_to?(:resolve_id) ? parent.resolve_id : nil
739
+ 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)
740
+ Channel.new(JSON.parse(response), @bot)
741
+ end
742
+
743
+ # Creates a role on this server which can then be modified. It will be initialized
744
+ # with the regular role defaults the client uses unless specified, i.e. name is "new role",
745
+ # permissions are the default, colour is the default etc.
746
+ # @param name [String] Name of the role to create.
747
+ # @param colour [Integer, ColourRGB, #combined] The primary colour of the role to create.
748
+ # @param hoist [true, false] whether members of this role should be displayed seperately in the members list.
749
+ # @param mentionable [true, false] whether this role can mentioned by anyone in the server.
750
+ # @param permissions [Integer, Array<Symbol>, Permissions, #bits] The permissions to write to the new role.
751
+ # @param icon [String, #read, nil] The base64 encoded image data, or a file like object that responds to #read.
752
+ # @param unicode_emoji [String, nil] The unicode emoji of the role to create, or nil.
753
+ # @param display_icon [String, File, #read, nil] The icon to display for the role. Overrides the **icon** and **unicode_emoji** parameters if passed.
754
+ # @param reason [String] The reason the for the creation of this role.
755
+ # @param secondary_colour [Integer, ColourRGB, nil] The secondary colour of the role to create.
756
+ # @param tertiary_colour [Integer, ColourRGB, nil] The tertiary colour of the role to create.
757
+ # @return [Role] the created role.
758
+ def create_role(name: 'new role', colour: 0, hoist: false, mentionable: false, permissions: 104_324_161, secondary_colour: nil, tertiary_colour: nil, icon: nil, unicode_emoji: nil, display_icon: nil, reason: nil)
759
+ colour = colour.respond_to?(:combined) ? colour.combined : colour
760
+
761
+ permissions = if permissions.is_a?(Array)
762
+ Permissions.bits(permissions)
763
+ elsif permissions.respond_to?(:bits)
764
+ permissions.bits
765
+ else
766
+ permissions
767
+ end
768
+
769
+ icon = icon.respond_to?(:read) ? OnyxCord.encode64(icon) : icon
770
+
771
+ colours = {
772
+ primary_color: colour&.to_i,
773
+ tertiary_color: tertiary_colour&.to_i,
774
+ secondary_color: secondary_colour&.to_i
775
+ }
776
+
777
+ if display_icon.is_a?(String)
778
+ unicode_emoji = display_icon
779
+ elsif display_icon.respond_to?(:read)
780
+ icon = OnyxCord.encode64(display_icon)
781
+ end
782
+
783
+ response = API::Server.create_role(@bot.token, @id, name, nil, hoist, mentionable, permissions&.to_s, reason, colours, icon, unicode_emoji)
784
+
785
+ role = Role.new(JSON.parse(response), @bot, self)
786
+ @roles[role.id] = role
787
+ end
788
+
789
+ # Adds a new custom emoji on this server.
790
+ # @param name [String] The name of emoji to create.
791
+ # @param image [String, #read] A base64 encoded string with the image data, or an object that responds to `#read`, such as `File`.
792
+ # @param roles [Array<Role, String, Integer>] An array of roles, or role IDs to be whitelisted for this emoji.
793
+ # @param reason [String] The reason the for the creation of this emoji.
794
+ # @return [Emoji] The emoji that has been added.
795
+ def add_emoji(name, image, roles = [], reason: nil)
796
+ image = image.respond_to?(:read) ? OnyxCord.encode64(image) : image
797
+
798
+ data = JSON.parse(API::Server.add_emoji(@bot.token, @id, image, name, roles.map(&:resolve_id), reason))
799
+ new_emoji = Emoji.new(data, @bot, self)
800
+ @emoji[new_emoji.id] = new_emoji
801
+ end
802
+
803
+ # Delete a custom emoji on this server
804
+ # @param emoji [Emoji, String, Integer] The emoji or emoji ID to be deleted.
805
+ # @param reason [String] The reason the for the deletion of this emoji.
806
+ def delete_emoji(emoji, reason: nil)
807
+ API::Server.delete_emoji(@bot.token, @id, emoji.resolve_id, reason)
808
+ end
809
+
810
+ # Changes the name and/or role whitelist of an emoji on this server.
811
+ # @param emoji [Emoji, String, Integer] The emoji or emoji ID to edit.
812
+ # @param name [String] The new name for the emoji.
813
+ # @param roles [Array<Role, String, Integer>] A new array of roles, or role IDs, to whitelist.
814
+ # @param reason [String] The reason for the editing of this emoji.
815
+ # @return [Emoji] The edited emoji.
816
+ def edit_emoji(emoji, name: nil, roles: nil, reason: nil)
817
+ emoji = @emoji[emoji.resolve_id]
818
+ data = JSON.parse(API::Server.edit_emoji(@bot.token, @id, emoji.resolve_id, name || emoji.name, (roles || emoji.roles).map(&:resolve_id), reason))
819
+ new_emoji = Emoji.new(data, @bot, self)
820
+ @emoji[new_emoji.id] = new_emoji
821
+ end
822
+
823
+ # The amount of emoji the server can have, based on its current Nitro Boost Level.
824
+ # @return [Integer] the max amount of emoji
825
+ def max_emoji
826
+ case @boost_level
827
+ when 1
828
+ 100
829
+ when 2
830
+ 150
831
+ when 3
832
+ 250
833
+ else
834
+ 50
835
+ end
836
+ end
837
+
838
+ # Searches a server for members that matches a username or a nickname.
839
+ # @param name [String] The username or nickname to search for.
840
+ # @param limit [Integer] The maximum number of members between 1-1000 to return. Returns 1 member by default.
841
+ # @return [Array<Member>, nil] An array of member objects that match the given parameters, or nil for no members.
842
+ def search_members(name:, limit: nil)
843
+ response = JSON.parse(API::Server.search_guild_members(@bot.token, @id, name, limit))
844
+ return nil if response.empty?
845
+
846
+ response.map { |mem| Member.new(mem, self, @bot) }
847
+ end
848
+
849
+ # Retrieve banned users from this server.
850
+ # @param limit [Integer] Number of users to return (up to maximum 1000, default 1000).
851
+ # @param before_id [Integer] Consider only users before given user id.
852
+ # @param after_id [Integer] Consider only users after given user id.
853
+ # @return [Array<ServerBan>] a list of banned users on this server and the reason they were banned.
854
+ def bans(limit: nil, before_id: nil, after_id: nil)
855
+ response = JSON.parse(API::Server.bans(@bot.token, @id, limit, before_id, after_id))
856
+ response.map do |e|
857
+ ServerBan.new(self, @bot.ensure_user(e['user']), e['reason'])
858
+ end
859
+ end
860
+
861
+ # Get the users who have been banned from the server.
862
+ # @param limit [Integer, nil] The max number of bans to return, or `nil` for no limit.
863
+ # @param after [User, Member, Time, Integer, String, nil] Get bans after this user ID.
864
+ # @param before [User, Member, Time, Integer, String, nil] Get bans before this user ID.
865
+ # @return [Array<ServerBan>] The users who have been banned from the server.
866
+ # @note When using the `before` parameter, bans will be sorted in descending order by user ID
867
+ # (newest users first), and in ascending order by user ID (oldest users first) otherwise.
868
+ def bans!(limit: 1000, before: nil, after: nil)
869
+ raise ArgumentError, "'before' and 'after' are mutually exclusive" if before && after
870
+
871
+ f_limit = limit && limit <= 1000 ? limit : 1000
872
+ f_after = after.is_a?(Time) ? IDObject.synthesize(after) : after&.resolve_id
873
+ f_before = before.is_a?(Time) ? IDObject.synthesize(before) : before&.resolve_id
874
+
875
+ get_bans = lambda do |before: nil, after: nil|
876
+ data = API::Server.bans(@bot.token, @id, f_limit, before&.id || f_before, after&.id || f_after)
877
+ JSON.parse(data).map { |ban| ServerBan.new(self, @bot.ensure_user(ban['user']), ban['reason']) }
878
+ end
879
+
880
+ paginator = Paginator.new(limit, before ? :up : :down) do |page|
881
+ if before
882
+ get_bans.call(before: page&.first&.user)
883
+ else
884
+ get_bans.call(after: page&.last&.user)
885
+ end
886
+ end
887
+
888
+ paginator.to_a
889
+ end
890
+
891
+ # Bans a user from this server.
892
+ # @param user [User, String, Integer] The user to ban.
893
+ # @param message_days [Integer] How many days worth of messages sent by the user should be deleted. This is deprecated and will be removed in 4.0.
894
+ # @param message_seconds [Integer] How many seconds of messages sent by the user should be deleted.
895
+ # @param reason [String] The reason the user is being banned.
896
+ def ban(user, message_days = 0, message_seconds: nil, reason: nil)
897
+ delete_messages = if message_days != 0 && message_days
898
+ message_days * 86_400
899
+ else
900
+ message_seconds || 0
901
+ end
902
+
903
+ API::Server.ban_user!(@bot.token, @id, user.resolve_id, delete_messages, reason)
904
+ end
905
+
906
+ # Unbans a previously banned user from this server.
907
+ # @param user [User, String, Integer] The user to unban.
908
+ # @param reason [String] The reason the user is being unbanned.
909
+ def unban(user, reason = nil)
910
+ API::Server.unban_user(@bot.token, @id, user.resolve_id, reason)
911
+ end
912
+
913
+ # Ban up to 200 users from this server in one go.
914
+ # @param users [Array<User, String, Integer>] Array of up to 200 users to ban.
915
+ # @param message_seconds [Integer] How many seconds of messages sent by the users should be deleted.
916
+ # @param reason [String] The reason these users are being banned.
917
+ # @return [BulkBan]
918
+ def bulk_ban(users:, message_seconds: 0, reason: nil)
919
+ raise ArgumentError, 'Can only ban between 1 and 200 users!' unless users.size.between?(1, 200)
920
+
921
+ return ban(users.first, 0, message_seconds: message_seconds, reason: reason) if users.size == 1
922
+
923
+ response = API::Server.bulk_ban(@bot.token, @id, users.map(&:resolve_id), message_seconds, reason)
924
+ BulkBan.new(JSON.parse(response), self, reason)
925
+ end
926
+
927
+ # Kicks a user from this server.
928
+ # @param user [User, String, Integer] The user to kick.
929
+ # @param reason [String] The reason the user is being kicked.
930
+ def kick(user, reason = nil)
931
+ API::Server.remove_member(@bot.token, @id, user.resolve_id, reason)
932
+ end
933
+
934
+ # Forcibly moves a user into a different voice channel.
935
+ # Only works if the bot has the permission needed and if the user is already connected to some voice channel on this server.
936
+ # @param user [User, String, Integer] The user to move.
937
+ # @param channel [Channel, String, Integer, nil] The voice channel to move into. (If nil, the user is disconnected from the voice channel)
938
+ def move(user, channel)
939
+ API::Server.update_member(@bot.token, @id, user.resolve_id, channel_id: channel&.resolve_id)
940
+ end
941
+
942
+ # Leave the server.
943
+ def leave
944
+ API::User.leave_server(@bot.token, @id)
945
+ end
946
+
947
+ # Sets the server's name.
948
+ # @param name [String] The new server name.
949
+ def name=(name)
950
+ modify(name: name)
951
+ end
952
+
953
+ # @return [Array<VoiceRegion>] collection of available voice regions to this guild
954
+ def available_voice_regions
955
+ return @available_voice_regions if @available_voice_regions
956
+
957
+ @available_voice_regions = {}
958
+
959
+ data = JSON.parse API::Server.regions(@bot.token, @id)
960
+ @available_voice_regions = data.map { |e| VoiceRegion.new e }
961
+ end
962
+
963
+ # @return [VoiceRegion, nil] voice region data for this server's region
964
+ # @note This may return `nil` if this server's voice region is deprecated.
965
+ def region
966
+ available_voice_regions.find { |e| e.id == @region_id }
967
+ end
968
+
969
+ # Moves the server to another region. This will cause a voice interruption of at most a second.
970
+ # @param region [String] The new region the server should be in.
971
+ def region=(region)
972
+ update_data(JSON.parse(API::Server.update!(@bot.token, @id, region: region.to_s)))
973
+ end
974
+
975
+ # Sets the server's icon.
976
+ # @param icon [String, #read, nil] The new icon, in base64-encoded JPG format.
977
+ def icon=(icon)
978
+ modify(icon: icon)
979
+ end
980
+
981
+ # Sets the server's AFK channel.
982
+ # @param afk_channel [Channel, nil] The new AFK channel, or `nil` if there should be none set.
983
+ def afk_channel=(afk_channel)
984
+ modify(afk_channel: afk_channel)
985
+ end
986
+
987
+ # Sets the server's system channel.
988
+ # @param system_channel [Channel, String, Integer, nil] The new system channel, or `nil` should it be disabled.
989
+ def system_channel=(system_channel)
990
+ modify(system_channel: system_channel)
991
+ end
992
+
993
+ # Sets the amount of time after which a user gets moved into the AFK channel.
994
+ # @param afk_timeout [Integer] The AFK timeout, in seconds.
995
+ def afk_timeout=(afk_timeout)
996
+ modify(afk_timeout: afk_timeout)
997
+ end
998
+
999
+ # A map of possible server verification levels to symbol names
1000
+ VERIFICATION_LEVELS = {
1001
+ none: 0,
1002
+ low: 1,
1003
+ medium: 2,
1004
+ high: 3,
1005
+ very_high: 4
1006
+ }.freeze
1007
+
1008
+ # @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').
1009
+ def verification_level
1010
+ VERIFICATION_LEVELS.key(@verification_level)
1011
+ end
1012
+
1013
+ # Sets the verification level of the server
1014
+ # @param level [Integer, Symbol] The verification level from 0-4 or Symbol (see {VERIFICATION_LEVELS})
1015
+ def verification_level=(level)
1016
+ modify(verification_level: level)
1017
+ end
1018
+
1019
+ # A map of possible message notification levels to symbol names
1020
+ NOTIFICATION_LEVELS = {
1021
+ all_messages: 0,
1022
+ only_mentions: 1
1023
+ }.freeze
1024
+
1025
+ # @return [Symbol] The default message notifications settings of the server (:all_messages = 'All messages', :only_mentions = 'Only @mentions').
1026
+ def default_message_notifications
1027
+ NOTIFICATION_LEVELS.key(@default_message_notifications)
1028
+ end
1029
+
1030
+ # Sets the default message notification level
1031
+ # @param notification_level [Integer, Symbol] The default message notification 0-1 or Symbol (see {NOTIFICATION_LEVELS})
1032
+ def default_message_notifications=(notification_level)
1033
+ modify(notification_level: notification_level)
1034
+ end
1035
+
1036
+ alias_method :notification_level=, :default_message_notifications=
1037
+
1038
+ # A map of possible content filter levels to symbol names
1039
+ FILTER_LEVELS = {
1040
+ disabled: 0,
1041
+ members_without_roles: 1,
1042
+ all_members: 2
1043
+ }.freeze
1044
+
1045
+ # @return [Symbol] The explicit content filter level of the server (:disabled = 'Don't scan any messages.', :members_without_roles = 'Scan messages for members without a role.', :all_members = 'Scan messages sent by all members.').
1046
+ def explicit_content_filter
1047
+ FILTER_LEVELS.key(@explicit_content_filter)
1048
+ end
1049
+
1050
+ alias_method :content_filter_level, :explicit_content_filter
1051
+
1052
+ # Sets the server content filter.
1053
+ # @param filter_level [Integer, Symbol] The content filter from 0-2 or Symbol (see {FILTER_LEVELS})
1054
+ def explicit_content_filter=(filter_level)
1055
+ modify(explicit_content_filter: filter_level)
1056
+ end
1057
+
1058
+ # A map of possible multi-factor authentication levels to symbol names
1059
+ MFA_LEVELS = {
1060
+ none: 0,
1061
+ elevated: 1
1062
+ }.freeze
1063
+
1064
+ # @return [Symbol] The multi-factor authentication level of the server (:none = 'no MFA/2FA requirement for moderation actions', :elevated = 'MFA/2FA is required for moderation actions')
1065
+ def mfa_level
1066
+ MFA_LEVELS.key @mfa_level
1067
+ end
1068
+
1069
+ # A map of possible NSFW levels to symbol names
1070
+ NSFW_LEVELS = {
1071
+ default: 0,
1072
+ explicit: 1,
1073
+ safe: 2,
1074
+ age_restricted: 3
1075
+ }.freeze
1076
+
1077
+ # @return [Symbol] The NSFW level of the server (:default = 'no NSFW level has been set', :explicit = 'the server may contain explicit content', :safe = 'the server does not contain NSFW content', :age_restricted = 'server membership is restricted to adults')
1078
+ def nsfw_level
1079
+ NSFW_LEVELS.key @nsfw_level
1080
+ end
1081
+
1082
+ # @return [true, false] whether this server has any emoji or not.
1083
+ def any_emoji?
1084
+ @emoji.any?
1085
+ end
1086
+
1087
+ alias_method :has_emoji?, :any_emoji?
1088
+ alias_method :emoji?, :any_emoji?
1089
+
1090
+ # Create an invite link using the server's vanity code.
1091
+ # @return [String, nil] The server's vanity invite URL, or `nil` if the server does not have a vanity invite code.
1092
+ def vanity_invite_url
1093
+ return unless @vanity_invite_code
1094
+
1095
+ "https://discord.gg/#{@vanity_invite_code}"
1096
+ end
1097
+
1098
+ alias_method :vanity_invite_link, :vanity_invite_url
1099
+
1100
+ # Check if the auto-moderation system has detected a raid.
1101
+ # @return [true, false] Whether or not Discord's anti-spam system has detected a raid in the server.
1102
+ def raid_detected?
1103
+ !@raid_detected_at.nil?
1104
+ end
1105
+
1106
+ # Check if the auto-moderation system has detected DM spam.
1107
+ # @return [true, false] Whether or not Discord's anti-spam system has detected dm-spam in the server.
1108
+ def dm_spam_detected?
1109
+ !@dm_spam_detected_at.nil?
1110
+ end
1111
+
1112
+ # Check if the server has disabled non-friend DMs.
1113
+ # @return [true, false] Whether or not the server has stopped member's who aren't friends from DMing each other.
1114
+ def dms_disabled?
1115
+ !@dms_disabled_until.nil? && @dms_disabled_until > Time.now
1116
+ end
1117
+
1118
+ # Check if the server has paused invites.
1119
+ # @return [true, false] Whether or not the server has stopped new members from joining, either via incident actions
1120
+ # or the `:invites_disabled` feature.
1121
+ def invites_disabled?
1122
+ (!@invites_disabled_until.nil? && @invites_disabled_until > Time.now) || @features.include?(:invites_disabled)
1123
+ end
1124
+
1125
+ # Requests a list of Webhooks on the server.
1126
+ # @return [Array<Webhook>] webhooks on the server.
1127
+ def webhooks
1128
+ webhooks = JSON.parse(API::Server.webhooks(@bot.token, @id))
1129
+ webhooks.map { |webhook| Webhook.new(webhook, @bot) }
1130
+ end
1131
+
1132
+ # Requests a list of Invites to the server.
1133
+ # @return [Array<Invite>] invites to the server.
1134
+ def invites
1135
+ invites = JSON.parse(API::Server.invites(@bot.token, @id))
1136
+ invites.map { |invite| Invite.new(invite, @bot) }
1137
+ end
1138
+
1139
+ # Get the scheduled events on the server.
1140
+ # @param bypass_cache [true, false] Whether the cached scheduled events
1141
+ # should be ignored and re-fetched via an HTTP request.
1142
+ # @return [Array<ScheduledEvent>] The scheduled events on the server.
1143
+ def scheduled_events(bypass_cache: false)
1144
+ process_scheduled_events(JSON.parse(API::Server.list_scheduled_events(@bot.token, @id, with_user_count: true))) if bypass_cache
1145
+
1146
+ @scheduled_events.values
1147
+ end
1148
+
1149
+ # Get a specific scheduled event on the server.
1150
+ # @param scheduled_event_id [Integer, String, ScheduledEvent] The scheduled event to get.
1151
+ # @param request [true, false] Whether to request the event from discord if it isn't cached.
1152
+ # @return [ScheduledEvent, nil] The scheduled event for the ID, or `nil` if it couldn't be found.
1153
+ def scheduled_event(scheduled_event_id, request: true)
1154
+ id = scheduled_event_id.resolve_id
1155
+ return @scheduled_events[id] if @scheduled_events[id]
1156
+ return nil unless request
1157
+
1158
+ event = JSON.parse(API::Server.get_scheduled_event(@bot.token, @id, id, with_user_count: true))
1159
+ scheduled_event = ScheduledEvent.new(event, self, @bot)
1160
+ @scheduled_events[scheduled_event.id] = scheduled_event
1161
+ rescue StandardError
1162
+ nil
1163
+ end
1164
+
1165
+ # Create a scheduled event on this server.
1166
+ # @param name [String] The 1-100 character name of the scheduled event to create.
1167
+ # @param start_time [Time] The start time of the scheduled event to create.
1168
+ # @param entity_type [Integer, Symbol] The entity type of the scheduled event to create.
1169
+ # @param end_time [Time, nil] The end time of the scheduled event to create.
1170
+ # @param channel [Integer, Channel, String, nil] The channel where the scheduled event will take place.
1171
+ # @param location [String, nil] The external location of the scheduled event to create.
1172
+ # @param description [String, nil] The 1-100 character description of the scheduled event to create.
1173
+ # @param cover [File, #read, nil] The cover image of the scheduled event to create.
1174
+ # @param recurrence_rule [#to_h, nil] The recurrence rule of the scheduled event to create.
1175
+ # @param reason [String, nil] The audit log reason for creating the scheduled event.
1176
+ # @yieldparam builder [ScheduledEvent::RecurrenceRule::Builder] An optional reccurence rule builder.
1177
+ # @return [ScheduledEvent] the scheduled event that was created.
1178
+ def create_scheduled_event(name:, start_time:, entity_type:, end_time: nil, channel: nil, location: nil, description: nil, cover: nil, recurrence_rule: nil, reason: nil)
1179
+ yield((builder = ScheduledEvent::RecurrenceRule::Builder.new)) if block_given?
1180
+
1181
+ options = {
1182
+ name: name,
1183
+ privacy_level: 2,
1184
+ scheduled_start_time: start_time&.iso8601,
1185
+ entity_type: ScheduledEvent::ENTITY_TYPES[entity_type] || entity_type,
1186
+ channel_id: channel&.resolve_id,
1187
+ entity_metadata: location ? { location: location } : nil,
1188
+ scheduled_end_time: end_time&.iso8601,
1189
+ description: description,
1190
+ image: cover.respond_to?(:read) ? OnyxCord.encode64(cover) : cover,
1191
+ recurrence_rule: block_given? ? builder.to_h : recurrence_rule&.to_h
1192
+ }
1193
+
1194
+ event = JSON.parse(API::Server.create_scheduled_event(@bot.token, @id, **options, reason: reason))
1195
+ scheduled_event = ScheduledEvent.new(event, self, @bot)
1196
+ @scheduled_events[scheduled_event.id] = scheduled_event
1197
+ end
1198
+
1199
+ # Processes a GUILD_MEMBERS_CHUNK packet, specifically the members field
1200
+ # @note For internal use only
1201
+ # @!visibility private
1202
+ def process_chunk(members, chunk_index, chunk_count)
1203
+ process_members(members)
1204
+ LOGGER.debug("Processed chunk #{chunk_index + 1}/#{chunk_count} server #{@id} - index #{chunk_index} - length #{members.length}")
1205
+
1206
+ return if chunk_index + 1 < chunk_count
1207
+
1208
+ LOGGER.debug("Finished chunking server #{@id}")
1209
+
1210
+ # Reset everything to normal
1211
+ @chunked = true
1212
+ end
1213
+
1214
+ # Get the AFK channel of the server.
1215
+ # @return [Channel, nil] the AFK voice channel of this server, or `nil` if none is set.
1216
+ def afk_channel
1217
+ @bot.channel(@afk_channel_id) if @afk_channel_id
1218
+ end
1219
+
1220
+ # Get the rules channel of the server.
1221
+ # @return [Channel, nil] The channel where community servers can display rules or guidelines, or `nil` if none is set.
1222
+ def rules_channel
1223
+ @bot.channel(@rules_channel_id) if @rules_channel_id
1224
+ end
1225
+
1226
+ # Get the system channel of the server.
1227
+ # @return [Channel, nil] The system channel (used for automatic welcome messages) of a server, or `nil` if none is set.
1228
+ def system_channel
1229
+ @bot.channel(@system_channel_id) if @system_channel_id
1230
+ end
1231
+
1232
+ # Get the safety alerts channel of the server.
1233
+ # @return [Channel, nil] The channel where Community servers receive safety alerts from Discord, or `nil` if none is set.
1234
+ def safety_alerts_channel
1235
+ @bot.channel(@safety_alerts_channel_id) if @safety_alerts_channel_id
1236
+ end
1237
+
1238
+ # Get the public updates channel of the server.
1239
+ # @return [Channel, nil] The channel where Community servers receive public updates from Discord, or `nil` if none is set.
1240
+ def public_updates_channel
1241
+ @bot.channel(@public_updates_channel_id) if @public_updates_channel_id
1242
+ end
1243
+
1244
+ # Modify the properties of the server.
1245
+ # @param name [String] The new 2-32 character name of the server.
1246
+ # @param verification_level [Symbol, Integer, nil] The new verification level of the server.
1247
+ # @param notification_level [Symbol, Integer, nil] The new default message notification level of the server.
1248
+ # @param explicit_content_filter [Symbol, Integer, nil] The new explicit content filter level of the server.
1249
+ # @param afk_channel [Channel, Integer, String, nil] The new AFK voice channel members should be automatically moved to.
1250
+ # @param afk_timeout [Integer] The new AFK timeout in seconds. Can be set to one of `60`, `300`, `900`, `1800`, or `3600`.
1251
+ # @param icon [#read, File, nil] The new icon of the server. Should be a file-like object that responds to `#read`.
1252
+ # @param splash [#read, File, nil] The new invite splash of the server. Should be a file-like object that responds to `#read`.
1253
+ # @param discovery_splash [#read, File, nil] The new discovery splash of the server. Should be a file-like object that responds to `#read`.
1254
+ # @param banner [#read, File, nil] The new banner of the server. Should be a file-like object that responds to `#read`.
1255
+ # @param system_channel [Channel, Integer, String, nil] The new channel where system messages should be sent.
1256
+ # @param system_channel_flags [Integer] The new system channel flags to set for the server's system channel expressed as a bitfield.
1257
+ # @param rules_channel [Channel, Integer, String, nil] The new channel where the server displays its rules or guidelines.
1258
+ # @param public_updates_channel [Channel, Integer, String, nil] The new channel where public updates should be sent.
1259
+ # @param locale [String, Symbol, nil] The new preferred locale of the server; primarily for community servers.
1260
+ # @param features [Array<String, Symbol>] The new features to set for the server.
1261
+ # @param description [String, nil] The new description of the server.
1262
+ # @param boost_progress_bar [true, false] Whether or not the server boosting progress bar should be visible.
1263
+ # @param safety_alerts_channel [Channel, Integer, String, nil] The new channel where safety alerts should be sent.
1264
+ # @param widget_enabled [true, false, nil] Whether or not the server's widget should be enabled.
1265
+ # @param widget_channel [Channel, Integer, String, nil] The new invite channel for the server's widget.
1266
+ # @param dms_disabled_until [Time, nil] The time at when non-friend direct messages will be enabled again.
1267
+ # @param invites_disabled_until [Time, nil] The time at when invites will no longer be disabled.
1268
+ # @param reason [String, nil] The reason to show in the server's audit log for modifying the server.
1269
+ # @return [nil]
1270
+ def modify(
1271
+ name: :undef, verification_level: :undef, notification_level: :undef, explicit_content_filter: :undef,
1272
+ afk_channel: :undef, afk_timeout: :undef, icon: :undef, splash: :undef, discovery_splash: :undef, banner: :undef,
1273
+ system_channel: :undef, system_channel_flags: :undef, rules_channel: :undef, public_updates_channel: :undef,
1274
+ locale: :undef, features: :undef, description: :undef, boost_progress_bar: :undef, safety_alerts_channel: :undef,
1275
+ widget_enabled: :undef, widget_channel: :undef, dms_disabled_until: :undef, invites_disabled_until: :undef,
1276
+ reason: nil
1277
+ )
1278
+ data = {
1279
+ name: name,
1280
+ verification_level: VERIFICATION_LEVELS[verification_level] || verification_level,
1281
+ default_message_notifications: NOTIFICATION_LEVELS[notification_level] || notification_level,
1282
+ explicit_content_filter: FILTER_LEVELS[explicit_content_filter] || explicit_content_filter,
1283
+ afk_channel_id: afk_channel == :undef ? afk_channel : afk_channel&.resolve_id,
1284
+ afk_timeout: afk_timeout,
1285
+ icon: icon.respond_to?(:read) ? OnyxCord.encode64(icon) : icon,
1286
+ splash: splash.respond_to?(:read) ? OnyxCord.encode64(splash) : splash,
1287
+ discovery_splash: discovery_splash.respond_to?(:read) ? OnyxCord.encode64(discovery_splash) : discovery_splash,
1288
+ banner: banner.respond_to?(:read) ? OnyxCord.encode64(banner) : banner,
1289
+ system_channel_id: system_channel == :undef ? system_channel : system_channel&.resolve_id,
1290
+ system_channel_flags: system_channel_flags,
1291
+ rules_channel_id: rules_channel == :undef ? rules_channel : rules_channel&.resolve_id,
1292
+ public_updates_channel_id: public_updates_channel == :undef ? public_updates_channel : public_updates_channel&.resolve_id,
1293
+ preferred_locale: locale,
1294
+ features: features == :undef ? features : features.map(&:upcase),
1295
+ description: description,
1296
+ premium_progress_bar_enabled: boost_progress_bar,
1297
+ safety_alerts_channel_id: safety_alerts_channel == :undef ? safety_alerts_channel : safety_alerts_channel&.resolve_id
1298
+ }
1299
+
1300
+ if widget_enabled != :undef || widget_channel != :undef
1301
+ widget_data = {
1302
+ enabled: widget_enabled,
1303
+ channel_id: widget_channel == :undef ? widget_channel : widget_channel&.resolve_id
1304
+ }
1305
+
1306
+ cache_widget_data(JSON.parse(API::Server.update_widget(@bot.token, @id, **widget_data, reason: reason)))
1307
+ end
1308
+
1309
+ if invites_disabled_until != :undef || dms_disabled_until != :undef
1310
+ incidents_data = {
1311
+ dms_disabled_until: dms_disabled_until == :undef ? @dms_disabled_until&.iso8601 : dms_disabled_until&.iso8601,
1312
+ invites_disabled_until: invites_disabled_until == :undef ? @invites_disabled_until&.iso8601 : invites_disabled_until&.iso8601
1313
+ }
1314
+
1315
+ process_incident_actions(JSON.parse(API::Server.update_incident_actions(@bot.token, @id, **incidents_data, reason: reason)))
1316
+ end
1317
+
1318
+ return unless data.any? { |_, value| value != :undef }
1319
+
1320
+ update_data(JSON.parse(API::Server.update!(@bot.token, @id, **data, reason: reason)))
1321
+ nil
1322
+ end
1323
+
1324
+ # Updates the cached data with new data
1325
+ # @note For internal use only
1326
+ # @!visibility private
1327
+ def update_data(new_data = nil)
1328
+ new_data ||= JSON.parse(API::Server.resolve(@bot.token, @id))
1329
+ @name = new_data['name']
1330
+ @icon_id = new_data['icon']
1331
+ @splash_id = new_data['splash']
1332
+ @discovery_splash_id = new_data['discovery_splash']
1333
+ @owner_id = new_data['owner_id'].to_i
1334
+ @region_id = new_data['region'] if new_data.key?('region')
1335
+
1336
+ @afk_timeout = new_data['afk_timeout']
1337
+ @afk_channel_id = new_data['afk_channel_id']&.to_i
1338
+
1339
+ @widget_enabled = new_data['widget_enabled'] if new_data.key?('widget_enabled')
1340
+ @widget_channel_id = new_data['widget_channel_id'] if new_data.key?('widget_channel_id')
1341
+
1342
+ @system_channel_flags = new_data['system_channel_flags']
1343
+ @system_channel_id = new_data['system_channel_id']&.to_i
1344
+
1345
+ @rules_channel_id = new_data['rules_channel_id']&.to_i
1346
+ @public_updates_channel_id = new_data['public_updates_channel_id']&.to_i
1347
+ @safety_alerts_channel_id = new_data['safety_alerts_channel_id']&.to_i
1348
+
1349
+ @mfa_level = new_data['mfa_level']
1350
+ @nsfw_level = new_data['nsfw_level']
1351
+ @verification_level = new_data['verification_level']
1352
+ @explicit_content_filter = new_data['explicit_content_filter']
1353
+ @default_message_notifications = new_data['default_message_notifications']
1354
+
1355
+ @features = new_data['features']&.map { |feature| feature.downcase.to_sym } || @features || []
1356
+ @max_presence_count = new_data['max_presences'] if new_data.key?('max_presences')
1357
+ @max_member_count = new_data['max_members'] if new_data.key?('max_members')
1358
+ @large = new_data.key?('large') ? new_data['large'] : (@large || false)
1359
+ @member_count = new_data['member_count'] || new_data['approximate_member_count'] || @member_count || 0
1360
+
1361
+ @vanity_url_code = new_data['vanity_url_code']
1362
+ @description = new_data['description']
1363
+ @banner_id = new_data['banner']
1364
+ @boost_level = new_data['premium_tier']
1365
+ @booster_count = new_data['premium_subscription_count'] || @booster_count || 0
1366
+ @locale = new_data['preferred_locale']
1367
+
1368
+ @max_video_channel_members = new_data['max_video_channel_users'] || @max_video_channel_members
1369
+ @max_stage_video_channel_members = new_data['max_stage_video_channel_users'] || @max_stage_video_channel_members
1370
+ @boost_progress_bar = new_data['premium_progress_bar_enabled']
1371
+
1372
+ process_channels(new_data['channels']) if new_data['channels']
1373
+ process_roles(new_data['roles']) if new_data['roles']
1374
+ process_emoji(new_data['emojis']) if new_data['emojis']
1375
+ process_members(new_data['members']) if new_data['members']
1376
+ process_presences(new_data['presences']) if new_data['presences']
1377
+ process_voice_states(new_data['voice_states']) if new_data['voice_states']
1378
+ process_active_threads(new_data['threads']) if new_data['threads']
1379
+ process_incident_actions(new_data['incidents_data']) if new_data.key?('incidents_data')
1380
+ process_scheduled_events(new_data['guild_scheduled_events']) if new_data['guild_scheduled_events']
1381
+ end
1382
+
1383
+ # Adds a channel to this server's cache
1384
+ # @note For internal use only
1385
+ # @!visibility private
1386
+ def add_channel(channel)
1387
+ @channels << channel
1388
+ @channels_by_id[channel.id] = channel
1389
+ end
1390
+
1391
+ # Deletes a channel from this server's cache
1392
+ # @note For internal use only
1393
+ # @!visibility private
1394
+ def delete_channel(id)
1395
+ @channels.reject! { |e| e.id == id }
1396
+ @channels_by_id.delete(id)
1397
+ end
1398
+
1399
+ # Updates the cached emoji data with new data
1400
+ # @note For internal use only
1401
+ # @!visibility private
1402
+ def update_emoji_data(new_data)
1403
+ @emoji = {}
1404
+ process_emoji(new_data['emojis'])
1405
+ end
1406
+
1407
+ # Updates the threads for this server's cache
1408
+ # @note For internal use only
1409
+ # @!visibility private
1410
+ def clear_threads(ids = nil)
1411
+ if ids.nil?
1412
+ @channels.reject!(&:thread?)
1413
+ @channels_by_id.delete_if { |_, channel| channel.thread? }
1414
+ else
1415
+ @channels.reject! { |channel| channel.thread? && ids.any?(channel.parent&.id) }
1416
+ @channels_by_id.delete_if { |_, channel| channel.thread? && ids.any?(channel.parent&.id) }
1417
+ end
1418
+ end
1419
+
1420
+ # The inspect method is overwritten to give more useful output
1421
+ def inspect
1422
+ "<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}>"
1423
+ end
1424
+
1425
+ private
1426
+
1427
+ def process_roles(roles)
1428
+ # Create roles
1429
+ @roles = {}
1430
+
1431
+ return unless roles
1432
+
1433
+ roles.each do |element|
1434
+ role = Role.new(element, @bot, self)
1435
+ @roles[role.id] = role
1436
+ end
1437
+ end
1438
+
1439
+ def process_emoji(emoji)
1440
+ return if emoji.empty?
1441
+
1442
+ emoji.each do |element|
1443
+ new_emoji = Emoji.new(element, @bot, self)
1444
+ @emoji[new_emoji.id] = new_emoji
1445
+ end
1446
+ end
1447
+
1448
+ def process_members(members)
1449
+ return unless members
1450
+
1451
+ members.each do |element|
1452
+ member = Member.new(element, self, @bot)
1453
+ @members[member.id] = member
1454
+ end
1455
+ end
1456
+
1457
+ def process_presences(presences)
1458
+ # Update user statuses with presence info
1459
+ return unless presences
1460
+
1461
+ presences.each do |element|
1462
+ next unless element['user']
1463
+
1464
+ user_id = element['user']['id'].to_i
1465
+ user = @members[user_id]
1466
+ if user
1467
+ user.update_presence(element)
1468
+ else
1469
+ LOGGER.warn "Rogue presence update! #{element['user']['id']} on #{@id}"
1470
+ end
1471
+ end
1472
+ end
1473
+
1474
+ def process_channels(channels)
1475
+ @channels = []
1476
+ @channels_by_id = {}
1477
+
1478
+ return unless channels
1479
+
1480
+ channels.each do |element|
1481
+ channel = @bot.ensure_channel(element, self)
1482
+ @channels << channel
1483
+ @channels_by_id[channel.id] = channel
1484
+ end
1485
+ end
1486
+
1487
+ def process_voice_states(voice_states)
1488
+ return unless voice_states
1489
+
1490
+ voice_states.each do |element|
1491
+ update_voice_state(element)
1492
+ end
1493
+ end
1494
+
1495
+ def process_active_threads(threads)
1496
+ @channels ||= []
1497
+ @channels_by_id ||= {}
1498
+
1499
+ return unless threads
1500
+
1501
+ threads.each do |element|
1502
+ thread = @bot.ensure_channel(element, self)
1503
+ @channels << thread
1504
+ @channels_by_id[thread.id] = thread
1505
+ end
1506
+ end
1507
+
1508
+ def process_incident_actions(incidents)
1509
+ incidents ||= {}
1510
+ @raid_detected_at = incidents['raid_detected_at'] ? Time.parse(incidents['raid_detected_at']) : nil
1511
+ @dms_disabled_until = incidents['dms_disabled_until'] ? Time.parse(incidents['dms_disabled_until']) : nil
1512
+ @dm_spam_detected_at = incidents['dm_spam_detected_at'] ? Time.parse(incidents['dm_spam_detected_at']) : nil
1513
+ @invites_disabled_until = incidents['invites_disabled_until'] ? Time.parse(incidents['invites_disabled_until']) : nil
1514
+ end
1515
+
1516
+ def process_scheduled_events(events)
1517
+ @scheduled_events = {}
1518
+
1519
+ return unless events
1520
+
1521
+ events.each do |element|
1522
+ event = ScheduledEvent.new(element, self, @bot)
1523
+ @scheduled_events[event.resolve_id] = event
1524
+ end
1525
+ end
1526
+ end
1527
+
1528
+ # A ban entry on a server.
1529
+ class ServerBan
1530
+ # @return [String, nil] the reason the user was banned, if provided
1531
+ attr_reader :reason
1532
+
1533
+ # @return [User] the user that was banned
1534
+ attr_reader :user
1535
+
1536
+ # @return [Server] the server this ban belongs to
1537
+ attr_reader :server
1538
+
1539
+ # @!visibility private
1540
+ def initialize(server, user, reason)
1541
+ @server = server
1542
+ @user = user
1543
+ @reason = reason
1544
+ end
1545
+
1546
+ # Removes this ban on the associated user in the server
1547
+ # @param reason [String] the reason for removing the ban
1548
+ def remove(reason = nil)
1549
+ @server.unban(user, reason)
1550
+ end
1551
+
1552
+ alias_method :unban, :remove
1553
+ alias_method :lift, :remove
1554
+ end
1555
+
1556
+ # A bulk ban entry on a server.
1557
+ class BulkBan
1558
+ # @return [Server] The server this bulk ban belongs to.
1559
+ attr_reader :server
1560
+
1561
+ # @return [String, nil] The reason these users were banned.
1562
+ attr_reader :reason
1563
+
1564
+ # @return [Array<Integer>] Array of user IDs that were banned.
1565
+ attr_reader :banned_users
1566
+
1567
+ # @return [Array<Integer>] Array of user IDs that couldn't be banned.
1568
+ attr_reader :failed_users
1569
+
1570
+ # @!visibility private
1571
+ def initialize(data, server, reason)
1572
+ @server = server
1573
+ @reason = reason
1574
+ @banned_users = data['banned_users']&.map(&:resolve_id) || []
1575
+ @failed_users = data['failed_users']&.map(&:resolve_id) || []
1576
+ end
1577
+ end
1578
+
1579
+ # A set of messages collected from a search query.
1580
+ class SearchedMessages
1581
+ include Enumerable
1582
+
1583
+ # @return [Array<Message>] the messages that matched the search query.
1584
+ attr_reader :messages
1585
+
1586
+ # @return [Integer] the total number of messages that matched the search query.
1587
+ attr_reader :total_results
1588
+
1589
+ # @!visibility private
1590
+ def initialize(messages, total, bot)
1591
+ @bot = bot
1592
+ @messages = messages
1593
+ @total_results = total
1594
+ end
1595
+
1596
+ # Get a single message that matched the search query by its index.
1597
+ # @param index [Integer] The index of the message to get from the array.
1598
+ # @return [Message] the message that was found at the specified index.
1599
+ def [](index)
1600
+ @messages[index]
1601
+ end
1602
+
1603
+ # Iterate over each message that matched the search query.
1604
+ # @return [Array<Message>, Enumerable] The array that was iterated over.
1605
+ def each(...)
1606
+ @messages.each(...)
1607
+ end
1608
+
1609
+ # @!visibility private
1610
+ def inspect
1611
+ "<SearchedMessages messages=[#{'...' if @messages.any?}] total_results=#{@total_results}>"
1612
+ end
1613
+ end
1614
+ end