discord_rda 0.1.3

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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +398 -0
  4. data/lib/discord_rda/bot.rb +842 -0
  5. data/lib/discord_rda/cache/configurable_cache.rb +283 -0
  6. data/lib/discord_rda/cache/entity_cache.rb +184 -0
  7. data/lib/discord_rda/cache/memory_store.rb +143 -0
  8. data/lib/discord_rda/cache/redis_store.rb +136 -0
  9. data/lib/discord_rda/cache/store.rb +56 -0
  10. data/lib/discord_rda/connection/gateway_client.rb +383 -0
  11. data/lib/discord_rda/connection/invalid_bucket.rb +205 -0
  12. data/lib/discord_rda/connection/rate_limiter.rb +280 -0
  13. data/lib/discord_rda/connection/request_queue.rb +340 -0
  14. data/lib/discord_rda/connection/reshard_manager.rb +328 -0
  15. data/lib/discord_rda/connection/rest_client.rb +316 -0
  16. data/lib/discord_rda/connection/rest_proxy.rb +165 -0
  17. data/lib/discord_rda/connection/scalable_rest_client.rb +526 -0
  18. data/lib/discord_rda/connection/shard_manager.rb +223 -0
  19. data/lib/discord_rda/core/async_runtime.rb +108 -0
  20. data/lib/discord_rda/core/configuration.rb +194 -0
  21. data/lib/discord_rda/core/logger.rb +188 -0
  22. data/lib/discord_rda/core/snowflake.rb +121 -0
  23. data/lib/discord_rda/entity/attachment.rb +88 -0
  24. data/lib/discord_rda/entity/base.rb +103 -0
  25. data/lib/discord_rda/entity/channel.rb +446 -0
  26. data/lib/discord_rda/entity/channel_builder.rb +280 -0
  27. data/lib/discord_rda/entity/color.rb +253 -0
  28. data/lib/discord_rda/entity/embed.rb +221 -0
  29. data/lib/discord_rda/entity/emoji.rb +89 -0
  30. data/lib/discord_rda/entity/factory.rb +99 -0
  31. data/lib/discord_rda/entity/guild.rb +619 -0
  32. data/lib/discord_rda/entity/member.rb +263 -0
  33. data/lib/discord_rda/entity/message.rb +405 -0
  34. data/lib/discord_rda/entity/message_builder.rb +369 -0
  35. data/lib/discord_rda/entity/role.rb +157 -0
  36. data/lib/discord_rda/entity/support.rb +294 -0
  37. data/lib/discord_rda/entity/user.rb +231 -0
  38. data/lib/discord_rda/entity/value_objects.rb +263 -0
  39. data/lib/discord_rda/event/auto_moderation.rb +294 -0
  40. data/lib/discord_rda/event/base.rb +986 -0
  41. data/lib/discord_rda/event/bus.rb +225 -0
  42. data/lib/discord_rda/event/scheduled_event.rb +257 -0
  43. data/lib/discord_rda/hot_reload_manager.rb +303 -0
  44. data/lib/discord_rda/interactions/application_command.rb +436 -0
  45. data/lib/discord_rda/interactions/command_system.rb +484 -0
  46. data/lib/discord_rda/interactions/components.rb +464 -0
  47. data/lib/discord_rda/interactions/interaction.rb +553 -0
  48. data/lib/discord_rda/plugin/analytics_plugin.rb +528 -0
  49. data/lib/discord_rda/plugin/base.rb +190 -0
  50. data/lib/discord_rda/plugin/registry.rb +126 -0
  51. data/lib/discord_rda/version.rb +5 -0
  52. data/lib/discord_rda.rb +70 -0
  53. metadata +302 -0
@@ -0,0 +1,619 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DiscordRDA
4
+ # Represents a Discord guild (server).
5
+ #
6
+ class Guild < Entity
7
+ attribute :name, type: :string
8
+ attribute :icon, type: :string
9
+ attribute :description, type: :string
10
+ attribute :splash, type: :string
11
+ attribute :discovery_splash, type: :string
12
+ attribute :owner_id, type: :snowflake
13
+ attribute :region, type: :string
14
+ attribute :afk_channel_id, type: :snowflake
15
+ attribute :afk_timeout, type: :integer
16
+ attribute :widget_enabled, type: :boolean, default: false
17
+ attribute :widget_channel_id, type: :snowflake
18
+ attribute :verification_level, type: :integer, default: 0
19
+ attribute :default_message_notifications, type: :integer, default: 0
20
+ attribute :explicit_content_filter, type: :integer, default: 0
21
+ attribute :roles, type: :array, default: []
22
+ attribute :emojis, type: :array, default: []
23
+ attribute :features, type: :array, default: []
24
+ attribute :mfa_level, type: :integer, default: 0
25
+ attribute :system_channel_id, type: :snowflake
26
+ attribute :system_channel_flags, type: :integer, default: 0
27
+ attribute :rules_channel_id, type: :snowflake
28
+ attribute :max_presences, type: :integer
29
+ attribute :max_members, type: :integer
30
+ attribute :vanity_url_code, type: :string
31
+ attribute :premium_tier, type: :integer, default: 0
32
+ attribute :premium_subscription_count, type: :integer, default: 0
33
+ attribute :preferred_locale, type: :string, default: 'en-US'
34
+ attribute :public_updates_channel_id, type: :snowflake
35
+ attribute :max_video_channel_users, type: :integer
36
+ attribute :approximate_member_count, type: :integer
37
+ attribute :approximate_presence_count, type: :integer
38
+ attribute :nsfw_level, type: :integer, default: 0
39
+ attribute :premium_progress_bar_enabled, type: :boolean, default: false
40
+
41
+ # Get the owner's snowflake ID
42
+ # @return [Snowflake] Owner ID
43
+ def owner_id
44
+ @owner_id ||= @raw_data['owner_id'] ? Snowflake.new(@raw_data['owner_id']) : nil
45
+ end
46
+
47
+ # Get icon URL
48
+ # @param format [String] Image format
49
+ # @param size [Integer] Image size
50
+ # @return [String, nil] Icon URL or nil if no icon
51
+ def icon_url(format: 'png', size: nil)
52
+ return nil unless @raw_data['icon']
53
+
54
+ url = "https://cdn.discordapp.com/icons/#{id}/#{@raw_data['icon']}.#{format}"
55
+ url += "?size=#{size}" if size
56
+ url
57
+ end
58
+
59
+ # Get splash URL (invite background)
60
+ # @param format [String] Image format
61
+ # @param size [Integer] Image size
62
+ # @return [String, nil] Splash URL or nil
63
+ def splash_url(format: 'png', size: nil)
64
+ return nil unless @raw_data['splash']
65
+
66
+ url = "https://cdn.discordapp.com/splashes/#{id}/#{@raw_data['splash']}.#{format}"
67
+ url += "?size=#{size}" if size
68
+ url
69
+ end
70
+
71
+ # Check if guild has a specific feature
72
+ # @param feature [String, Symbol] Feature name
73
+ # @return [Boolean] True if feature is enabled
74
+ def feature?(feature)
75
+ features.include?(feature.to_s.upcase)
76
+ end
77
+
78
+ # Get available features
79
+ # @return [Array<String>] Enabled features
80
+ def features
81
+ @raw_data['features'] || []
82
+ end
83
+
84
+ # Check if guild is large (250+ members)
85
+ # @return [Boolean] True if large
86
+ def large?
87
+ (@raw_data['large'] || @raw_data['member_count'].to_i >= 250)
88
+ end
89
+
90
+ # Get member count
91
+ # @return [Integer] Member count
92
+ def member_count
93
+ @raw_data['member_count'] || @raw_data['approximate_member_count'] || 0
94
+ end
95
+
96
+ # Get verification level name
97
+ # @return [String] Verification level
98
+ def verification_level_name
99
+ %w[none low medium high very_high][verification_level] || 'unknown'
100
+ end
101
+
102
+ # Get default message notifications setting name
103
+ # @return [String] Notification setting
104
+ def default_message_notifications_name
105
+ %w[all_messages only_mentions][default_message_notifications] || 'unknown'
106
+ end
107
+
108
+ # Get explicit content filter name
109
+ # @return [String] Content filter setting
110
+ def explicit_content_filter_name
111
+ %w[disabled_members_without_roles all_members][explicit_content_filter] || 'unknown'
112
+ end
113
+
114
+ # Get MFA level name
115
+ # @return [String] MFA level
116
+ def mfa_level_name
117
+ %w[none elevated][mfa_level] || 'unknown'
118
+ end
119
+
120
+ # Get premium tier name (boost level)
121
+ # @return [String] Premium tier
122
+ def premium_tier_name
123
+ %w[none tier_1 tier_2 tier_3][premium_tier] || 'unknown'
124
+ end
125
+
126
+ # Get NSFW level name
127
+ # @return [String] NSFW level
128
+ def nsfw_level_name
129
+ %w[default explicit safe age_restricted][nsfw_level] || 'unknown'
130
+ end
131
+
132
+ # Check if community guild
133
+ # @return [Boolean] True if community feature enabled
134
+ def community?
135
+ feature?(:community)
136
+ end
137
+
138
+ # Check if partnered guild
139
+ # @return [Boolean] True if partnered
140
+ def partnered?
141
+ feature?(:partnered)
142
+ end
143
+
144
+ # Check if verified guild
145
+ # @return [Boolean] True if verified
146
+ def verified?
147
+ feature?(:verified)
148
+ end
149
+
150
+ # Get approximate member count
151
+ # @return [Integer, nil] Approximate member count from preview
152
+ def approximate_member_count
153
+ @raw_data['approximate_member_count']
154
+ end
155
+
156
+ # Get approximate presence count
157
+ # @return [Integer, nil] Approximate online members
158
+ def approximate_presence_count
159
+ @raw_data['approximate_presence_count']
160
+ end
161
+
162
+ # Get vanity invite URL
163
+ # @return [String, nil] Vanity URL or nil
164
+ def vanity_url
165
+ return nil unless @raw_data['vanity_url_code']
166
+
167
+ "https://discord.gg/#{@raw_data['vanity_url_code']}"
168
+ end
169
+
170
+ # Get Role objects from raw data
171
+ # @return [Array<Role>] Guild roles
172
+ def role_objects
173
+ (@raw_data['roles'] || []).map { |r| Role.new(r.merge('guild_id' => id.to_s)) }
174
+ end
175
+
176
+ # Get a role by ID
177
+ # @param role_id [String, Snowflake] Role ID
178
+ # @return [Role, nil] Role or nil
179
+ def role(role_id)
180
+ role_data = (@raw_data['roles'] || []).find { |r| r['id'] == role_id.to_s }
181
+ Role.new(role_data.merge('guild_id' => id.to_s)) if role_data
182
+ end
183
+
184
+ # Get Emoji objects from raw data
185
+ # @return [Array<Emoji>] Guild emojis
186
+ def emoji_objects
187
+ (@raw_data['emojis'] || []).map { |e| Emoji.new(e) }
188
+ end
189
+
190
+ # Get discovery splash URL
191
+ # @param format [String] Image format
192
+ # @param size [Integer] Image size
193
+ # @return [String, nil] Discovery splash URL or nil
194
+ def discovery_splash_url(format: 'png', size: nil)
195
+ return nil unless @raw_data['discovery_splash']
196
+
197
+ url = "https://cdn.discordapp.com/discovery-splashes/#{id}/#{@raw_data['discovery_splash']}.#{format}"
198
+ url += "?size=#{size}" if size
199
+ url
200
+ end
201
+
202
+ # Check if guild has banner feature
203
+ # @return [Boolean] True if banner feature enabled
204
+ def banner_feature?
205
+ feature?(:banner)
206
+ end
207
+
208
+ # Check if guild has invite splash feature
209
+ # @return [Boolean] True if invite splash enabled
210
+ def invite_splash?
211
+ feature?(:invite_splash)
212
+ end
213
+
214
+ # Check if guild has animated banner
215
+ # @return [Boolean] True if animated banner feature enabled
216
+ def animated_banner?
217
+ feature?(:animated_banner)
218
+ end
219
+
220
+ # Check if guild has animated icon
221
+ # @return [Boolean] True if animated icon feature enabled
222
+ def animated_icon?
223
+ feature?(:animated_icon)
224
+ end
225
+
226
+ # Get boost count as tier name
227
+ # @return [String] Boost tier
228
+ def boost_tier
229
+ premium_tier_name
230
+ end
231
+
232
+ # Get number of boosts
233
+ # @return [Integer] Boost count
234
+ def boost_count
235
+ premium_subscription_count
236
+ end
237
+
238
+ # Check if boost progress bar is enabled
239
+ # @return [Boolean] True if enabled
240
+ def boost_progress_bar?
241
+ premium_progress_bar_enabled
242
+ end
243
+
244
+ # Get system channel flags as array
245
+ # @return [Array<Symbol>] Enabled flags
246
+ def system_channel_flags_list
247
+ flags = []
248
+ return flags unless system_channel_flags
249
+
250
+ flags << :suppress_join_notifications if system_channel_flags & 1 == 1
251
+ flags << :suppress_premium_subscriptions if system_channel_flags & 2 == 2
252
+ flags << :suppress_guild_reminder_notifications if system_channel_flags & 4 == 4
253
+ flags << :suppress_join_notification_replies if system_channel_flags & 8 == 8
254
+ flags << :suppress_role_subscription_purchase_notifications if system_channel_flags & 16 == 16
255
+ flags << :suppress_role_subscription_purchase_notification_replies if system_channel_flags & 32 == 32
256
+ flags
257
+ end
258
+
259
+ # Get preferred locale as symbol
260
+ # @return [Symbol] Locale
261
+ def locale
262
+ preferred_locale&.gsub('-', '_')&.downcase&.to_sym
263
+ end
264
+
265
+ # Check if guild is available (not unavailable due to outage)
266
+ # @return [Boolean] True if available
267
+ def available?
268
+ !@raw_data['unavailable']
269
+ end
270
+
271
+ # Check if guild is unavailable
272
+ # @return [Boolean] True if unavailable
273
+ def unavailable?
274
+ @raw_data['unavailable'] || false
275
+ end
276
+
277
+ # Class-level API client
278
+ class << self
279
+ attr_accessor :api
280
+ end
281
+
282
+ # Fetch a member from this guild
283
+ # @param user_id [String, Snowflake] User ID
284
+ # @return [Member, nil] Member or nil if not found
285
+ def fetch_member(user_id)
286
+ return nil unless self.class.api
287
+
288
+ data = self.class.api.get("/guilds/#{id}/members/#{user_id}")
289
+ Member.new(data.merge('guild_id' => id.to_s))
290
+ rescue RestClient::NotFoundError
291
+ nil
292
+ end
293
+
294
+ # Fetch members from this guild (simplified pagination)
295
+ # @param limit [Integer] Max members (1-1000)
296
+ # @param after [String, Snowflake] Get members after this user ID
297
+ # @return [Array<Member>] Guild members
298
+ def fetch_members(limit: 100, after: nil)
299
+ return [] unless self.class.api
300
+
301
+ params = { limit: limit }
302
+ params[:after] = after.to_s if after
303
+
304
+ data = self.class.api.get("/guilds/#{id}/members", params: params)
305
+ data.map { |m| Member.new(m.merge('guild_id' => id.to_s)) }
306
+ end
307
+
308
+ # Fetch channels in this guild
309
+ # @return [Array<Channel>] Guild channels
310
+ def fetch_channels
311
+ return [] unless self.class.api
312
+
313
+ data = self.class.api.get("/guilds/#{id}/channels")
314
+ data.map { |c| Channel.new(c) }
315
+ end
316
+
317
+ # Fetch roles in this guild
318
+ # @return [Array<Role>] Guild roles
319
+ def fetch_roles
320
+ return [] unless self.class.api
321
+
322
+ data = self.class.api.get("/guilds/#{id}/roles")
323
+ data.map { |r| Role.new(r.merge('guild_id' => id.to_s)) }
324
+ end
325
+
326
+ # Fetch bans in this guild
327
+ # @param limit [Integer] Max bans (1-1000)
328
+ # @return [Array<Hash>] Bans with user and reason
329
+ def fetch_bans(limit: 100)
330
+ return [] unless self.class.api
331
+
332
+ data = self.class.api.get("/guilds/#{id}/bans", params: { limit: limit })
333
+ data.map { |b| { user: User.new(b['user']), reason: b['reason'] } }
334
+ end
335
+
336
+ # Fetch active invites for this guild
337
+ # @return [Array<Hash>] Guild invites
338
+ def fetch_invites
339
+ return [] unless self.class.api
340
+
341
+ self.class.api.get("/guilds/#{id}/invites")
342
+ end
343
+
344
+ # Fetch guild preview (for lurkable guilds)
345
+ # @return [Hash, nil] Guild preview data
346
+ def fetch_preview
347
+ return nil unless self.class.api
348
+
349
+ self.class.api.get("/guilds/#{id}/preview")
350
+ rescue RestClient::NotFoundError
351
+ nil
352
+ end
353
+
354
+ # Fetch welcome screen
355
+ # @return [Hash, nil] Welcome screen data
356
+ def fetch_welcome_screen
357
+ return nil unless self.class.api
358
+
359
+ self.class.api.get("/guilds/#{id}/welcome-screen")
360
+ rescue RestClient::NotFoundError
361
+ nil
362
+ end
363
+
364
+ # Fetch onboarding settings
365
+ # @return [Hash, nil] Onboarding data
366
+ def fetch_onboarding
367
+ return nil unless self.class.api
368
+
369
+ self.class.api.get("/guilds/#{id}/onboarding")
370
+ rescue RestClient::NotFoundError
371
+ nil
372
+ end
373
+
374
+ # Fetch voice regions for this guild
375
+ # @return [Array<Hash>] Voice regions
376
+ def fetch_voice_regions
377
+ return [] unless self.class.api
378
+
379
+ self.class.api.get("/guilds/#{id}/regions")
380
+ end
381
+
382
+ # Fetch webhooks for this guild
383
+ # @return [Array<Hash>] Guild webhooks
384
+ def fetch_webhooks
385
+ return [] unless self.class.api
386
+
387
+ self.class.api.get("/guilds/#{id}/webhooks")
388
+ end
389
+
390
+ # Fetch integrations for this guild
391
+ # @return [Array<Hash>] Guild integrations
392
+ def fetch_integrations
393
+ return [] unless self.class.api
394
+
395
+ self.class.api.get("/guilds/#{id}/integrations")
396
+ end
397
+
398
+ # Fetch stickers for this guild
399
+ # @return [Array<Sticker>] Guild stickers
400
+ def fetch_stickers
401
+ return [] unless self.class.api
402
+
403
+ data = self.class.api.get("/guilds/#{id}/stickers")
404
+ data.map { |s| Sticker.new(s) }
405
+ end
406
+
407
+ # Fetch a specific guild sticker
408
+ # @param sticker_id [String, Snowflake] Sticker ID
409
+ # @return [Sticker, nil] Sticker or nil
410
+ def fetch_sticker(sticker_id)
411
+ return nil unless self.class.api
412
+
413
+ data = self.class.api.get("/guilds/#{id}/stickers/#{sticker_id}")
414
+ Sticker.new(data)
415
+ rescue RestClient::NotFoundError
416
+ nil
417
+ end
418
+
419
+ # Create a guild sticker
420
+ # @param name [String] Sticker name (2-30 characters)
421
+ # @param description [String] Sticker description (2-100 characters, optional for guild stickers)
422
+ # @param tags [String] Sticker tags (comma-separated, 2-200 characters total)
423
+ # @param file [File, String, IO] Sticker file (PNG, APNG, Lottie, or GIF, max 512KB, 320x320)
424
+ # @param reason [String] Audit log reason
425
+ # @return [Sticker] Created sticker
426
+ def create_sticker(name:, description:, tags:, file:, reason: nil)
427
+ return nil unless self.class.api
428
+
429
+ headers = {}
430
+ headers['X-Audit-Log-Reason'] = CGI.escape(reason) if reason
431
+
432
+ # Determine content type based on file extension
433
+ file_path = file.respond_to?(:path) ? file.path : file.to_s
434
+ ext = File.extname(file_path).downcase
435
+
436
+ content_type = case ext
437
+ when '.png' then 'image/png'
438
+ when '.apng' then 'image/apng'
439
+ when '.gif' then 'image/gif'
440
+ when '.json' then 'application/json'
441
+ else 'application/octet-stream'
442
+ end
443
+
444
+ # Create a file wrapper with proper metadata
445
+ file_wrapper = if file.respond_to?(:read)
446
+ file
447
+ else
448
+ File.open(file, 'rb')
449
+ end
450
+
451
+ # Set content type if not already set
452
+ unless file_wrapper.respond_to?(:content_type)
453
+ def file_wrapper.content_type
454
+ @content_type ||= 'application/octet-stream'
455
+ end
456
+ file_wrapper.instance_variable_set(:@content_type, content_type)
457
+ end
458
+
459
+ unless file_wrapper.respond_to?(:original_filename)
460
+ def file_wrapper.original_filename
461
+ @original_filename ||= 'sticker.png'
462
+ end
463
+ file_wrapper.instance_variable_set(:@original_filename, File.basename(file_path))
464
+ end
465
+
466
+ payload = {
467
+ name: name,
468
+ description: description,
469
+ tags: tags
470
+ }
471
+
472
+ data = self.class.api.post(
473
+ "/guilds/#{id}/stickers",
474
+ body: payload,
475
+ files: { 'file' => file_wrapper },
476
+ headers: headers
477
+ )
478
+
479
+ Sticker.new(data)
480
+ ensure
481
+ file_wrapper.close if file_wrapper.respond_to?(:close) && !file_wrapper.closed? && file_wrapper != file
482
+ end
483
+
484
+ # Modify a guild sticker
485
+ # @param sticker_id [String, Snowflake] Sticker ID
486
+ # @param name [String] New name
487
+ # @param description [String] New description
488
+ # @param tags [String] New tags
489
+ # @param reason [String] Audit log reason
490
+ # @return [Sticker] Updated sticker
491
+ def modify_sticker(sticker_id, name: nil, description: nil, tags: nil, reason: nil)
492
+ return nil unless self.class.api
493
+
494
+ headers = {}
495
+ headers['X-Audit-Log-Reason'] = CGI.escape(reason) if reason
496
+
497
+ body = { name: name, description: description, tags: tags }.compact
498
+
499
+ data = self.class.api.patch("/guilds/#{id}/stickers/#{sticker_id}", body: body, headers: headers)
500
+ Sticker.new(data)
501
+ end
502
+
503
+ # Delete a guild sticker
504
+ # @param sticker_id [String, Snowflake] Sticker ID
505
+ # @param reason [String] Audit log reason
506
+ # @return [void]
507
+ def delete_sticker(sticker_id, reason: nil)
508
+ return unless self.class.api
509
+
510
+ headers = {}
511
+ headers['X-Audit-Log-Reason'] = CGI.escape(reason) if reason
512
+
513
+ self.class.api.delete("/guilds/#{id}/stickers/#{sticker_id}", headers: headers)
514
+ end
515
+
516
+ # Fetch widget settings
517
+ # @return [Hash, nil] Widget settings
518
+ def fetch_widget_settings
519
+ return nil unless self.class.api
520
+
521
+ self.class.api.get("/guilds/#{id}/widget")
522
+ rescue RestClient::NotFoundError
523
+ nil
524
+ end
525
+
526
+ # Get widget URL
527
+ # @return [String] Widget URL
528
+ def widget_url
529
+ "https://discord.com/widget?id=#{id}&theme=dark"
530
+ end
531
+
532
+ # Get guild vanity URL with code
533
+ # @return [String, nil] Vanity URL
534
+ def vanity_invite_url
535
+ return nil unless vanity_url_code
536
+
537
+ "https://discord.gg/#{vanity_url_code}"
538
+ end
539
+
540
+ # Check if guild has vanity URL feature
541
+ # @return [Boolean] True if has vanity URL
542
+ def has_vanity_url?
543
+ !vanity_url_code.nil? && !vanity_url_code.empty?
544
+ end
545
+
546
+ # Check if guild has description
547
+ # @return [Boolean] True if has description
548
+ def has_description?
549
+ description && !description.empty?
550
+ end
551
+
552
+ # Get role count
553
+ # @return [Integer] Number of roles
554
+ def role_count
555
+ @raw_data['roles']&.length || 0
556
+ end
557
+
558
+ # Get emoji count
559
+ # @return [Integer] Number of emojis
560
+ def emoji_count
561
+ @raw_data['emojis']&.length || 0
562
+ end
563
+
564
+ # Check if guild is likely community server
565
+ # @return [Boolean] True if community guild
566
+ def likely_community?
567
+ community? || rules_channel_id || public_updates_channel_id
568
+ end
569
+
570
+ # Get moderation level description
571
+ # @return [String] Moderation level
572
+ def moderation_level
573
+ verification_level_name
574
+ end
575
+
576
+ # Check if guild requires verification
577
+ # @return [Boolean] True if requires verification
578
+ def requires_verification?
579
+ verification_level > 0
580
+ end
581
+
582
+ # Check if guild has 2FA requirement for moderation
583
+ # @return [Boolean] True if requires 2FA
584
+ def requires_2fa?
585
+ mfa_level == 1
586
+ end
587
+
588
+ # Check if guild has explicit content filter enabled
589
+ # @return [Boolean] True if has content filter
590
+ def has_content_filter?
591
+ explicit_content_filter > 0
592
+ end
593
+
594
+ # Check if guild has NSFW content allowed
595
+ # @return [Boolean] True if NSFW allowed
596
+ def nsfw_allowed?
597
+ nsfw_level > 0
598
+ end
599
+
600
+ # Get human-readable guild summary
601
+ # @return [Hash] Guild summary
602
+ def summary
603
+ {
604
+ id: id.to_s,
605
+ name: name,
606
+ member_count: member_count,
607
+ online_count: approximate_presence_count,
608
+ boost_tier: boost_tier,
609
+ boost_count: boost_count,
610
+ features: features,
611
+ large: large?,
612
+ community: community?,
613
+ partnered: partnered?,
614
+ verified: verified?,
615
+ available: available?
616
+ }
617
+ end
618
+ end
619
+ end