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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +398 -0
- data/lib/discord_rda/bot.rb +842 -0
- data/lib/discord_rda/cache/configurable_cache.rb +283 -0
- data/lib/discord_rda/cache/entity_cache.rb +184 -0
- data/lib/discord_rda/cache/memory_store.rb +143 -0
- data/lib/discord_rda/cache/redis_store.rb +136 -0
- data/lib/discord_rda/cache/store.rb +56 -0
- data/lib/discord_rda/connection/gateway_client.rb +383 -0
- data/lib/discord_rda/connection/invalid_bucket.rb +205 -0
- data/lib/discord_rda/connection/rate_limiter.rb +280 -0
- data/lib/discord_rda/connection/request_queue.rb +340 -0
- data/lib/discord_rda/connection/reshard_manager.rb +328 -0
- data/lib/discord_rda/connection/rest_client.rb +316 -0
- data/lib/discord_rda/connection/rest_proxy.rb +165 -0
- data/lib/discord_rda/connection/scalable_rest_client.rb +526 -0
- data/lib/discord_rda/connection/shard_manager.rb +223 -0
- data/lib/discord_rda/core/async_runtime.rb +108 -0
- data/lib/discord_rda/core/configuration.rb +194 -0
- data/lib/discord_rda/core/logger.rb +188 -0
- data/lib/discord_rda/core/snowflake.rb +121 -0
- data/lib/discord_rda/entity/attachment.rb +88 -0
- data/lib/discord_rda/entity/base.rb +103 -0
- data/lib/discord_rda/entity/channel.rb +446 -0
- data/lib/discord_rda/entity/channel_builder.rb +280 -0
- data/lib/discord_rda/entity/color.rb +253 -0
- data/lib/discord_rda/entity/embed.rb +221 -0
- data/lib/discord_rda/entity/emoji.rb +89 -0
- data/lib/discord_rda/entity/factory.rb +99 -0
- data/lib/discord_rda/entity/guild.rb +619 -0
- data/lib/discord_rda/entity/member.rb +263 -0
- data/lib/discord_rda/entity/message.rb +405 -0
- data/lib/discord_rda/entity/message_builder.rb +369 -0
- data/lib/discord_rda/entity/role.rb +157 -0
- data/lib/discord_rda/entity/support.rb +294 -0
- data/lib/discord_rda/entity/user.rb +231 -0
- data/lib/discord_rda/entity/value_objects.rb +263 -0
- data/lib/discord_rda/event/auto_moderation.rb +294 -0
- data/lib/discord_rda/event/base.rb +986 -0
- data/lib/discord_rda/event/bus.rb +225 -0
- data/lib/discord_rda/event/scheduled_event.rb +257 -0
- data/lib/discord_rda/hot_reload_manager.rb +303 -0
- data/lib/discord_rda/interactions/application_command.rb +436 -0
- data/lib/discord_rda/interactions/command_system.rb +484 -0
- data/lib/discord_rda/interactions/components.rb +464 -0
- data/lib/discord_rda/interactions/interaction.rb +553 -0
- data/lib/discord_rda/plugin/analytics_plugin.rb +528 -0
- data/lib/discord_rda/plugin/base.rb +190 -0
- data/lib/discord_rda/plugin/registry.rb +126 -0
- data/lib/discord_rda/version.rb +5 -0
- data/lib/discord_rda.rb +70 -0
- 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
|