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,446 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DiscordRDA
4
+ # Represents a Discord channel.
5
+ # Can be text, voice, category, DM, thread, etc.
6
+ #
7
+ class Channel < Entity
8
+ # Channel types
9
+ TYPES = {
10
+ guild_text: 0,
11
+ dm: 1,
12
+ guild_voice: 2,
13
+ group_dm: 3,
14
+ guild_category: 4,
15
+ guild_announcement: 5,
16
+ announcement_thread: 10,
17
+ public_thread: 11,
18
+ private_thread: 12,
19
+ guild_stage_voice: 13,
20
+ guild_directory: 14,
21
+ guild_forum: 15,
22
+ guild_media: 16
23
+ }.freeze
24
+
25
+ attribute :type, type: :integer
26
+ attribute :guild_id, type: :snowflake
27
+ attribute :position, type: :integer, default: 0
28
+ attribute :name, type: :string
29
+ attribute :topic, type: :string
30
+ attribute :nsfw, type: :boolean, default: false
31
+ attribute :last_message_id, type: :snowflake
32
+ attribute :bitrate, type: :integer
33
+ attribute :user_limit, type: :integer
34
+ attribute :rate_limit_per_user, type: :integer, default: 0
35
+ attribute :recipients, type: :array, default: []
36
+ attribute :icon, type: :string
37
+ attribute :owner_id, type: :snowflake
38
+ attribute :application_id, type: :snowflake
39
+ attribute :parent_id, type: :snowflake
40
+ attribute :last_pin_timestamp, type: :time
41
+ attribute :rtc_region, type: :string
42
+ attribute :video_quality_mode, type: :integer, default: 1
43
+ attribute :message_count, type: :integer
44
+ attribute :member_count, type: :integer
45
+ attribute :thread_metadata, type: :hash
46
+ attribute :member, type: :hash
47
+ attribute :default_auto_archive_duration, type: :integer
48
+ attribute :permissions, type: :string
49
+ attribute :flags, type: :integer, default: 0
50
+ attribute :total_message_sent, type: :integer
51
+ attribute :available_tags, type: :array, default: []
52
+ attribute :applied_tags, type: :array, default: []
53
+ attribute :default_reaction_emoji, type: :hash
54
+ attribute :default_thread_rate_limit_per_user, type: :integer
55
+ attribute :default_sort_order, type: :integer
56
+ attribute :default_forum_layout, type: :integer
57
+
58
+ # Class-level API client for REST operations
59
+ class << self
60
+ attr_accessor :api
61
+ end
62
+
63
+ # Get channel type as symbol
64
+ # @return [Symbol] Channel type
65
+ def channel_type
66
+ TYPES.key(type) || :unknown
67
+ end
68
+
69
+ # Check if this is a text channel
70
+ # @return [Boolean] True if text channel
71
+ def text?
72
+ type == 0 || type == 5
73
+ end
74
+
75
+ # Check if this is a voice channel
76
+ # @return [Boolean] True if voice channel
77
+ def voice?
78
+ type == 2 || type == 13
79
+ end
80
+
81
+ # Check if this is a DM channel
82
+ # @return [Boolean] True if DM
83
+ def dm?
84
+ type == 1
85
+ end
86
+
87
+ # Check if this is a group DM
88
+ # @return [Boolean] True if group DM
89
+ def group_dm?
90
+ type == 3
91
+ end
92
+
93
+ # Check if this is a category
94
+ # @return [Boolean] True if category
95
+ def category?
96
+ type == 4
97
+ end
98
+
99
+ # Check if this is a thread
100
+ # @return [Boolean] True if thread
101
+ def thread?
102
+ type == 10 || type == 11 || type == 12
103
+ end
104
+
105
+ # Check if this is a forum channel
106
+ # @return [Boolean] True if forum
107
+ def forum?
108
+ type == 15
109
+ end
110
+
111
+ # Check if this is a media channel
112
+ # @return [Boolean] True if media channel
113
+ def media?
114
+ type == 16
115
+ end
116
+
117
+ # Check if this is an announcement channel
118
+ # @return [Boolean] True if announcement
119
+ def announcement?
120
+ type == 5
121
+ end
122
+
123
+ # Get mention string for the channel
124
+ # @return [String] Channel mention
125
+ def mention
126
+ "<##{id}>"
127
+ end
128
+
129
+ # Get the jump URL for this channel
130
+ # @param guild_id [Snowflake, String] Optional guild ID
131
+ # @return [String] Jump URL
132
+ def jump_url(guild_id: nil)
133
+ gid = guild_id || self.guild_id
134
+ "https://discord.com/channels/#{gid}/#{id}"
135
+ end
136
+
137
+ # Get last message timestamp
138
+ # @return [Time, nil] Last message time
139
+ def last_message_at
140
+ last_message_id&.timestamp
141
+ end
142
+
143
+ # Check if channel is NSFW
144
+ # @return [Boolean] True if NSFW
145
+ def nsfw?
146
+ nsfw
147
+ end
148
+
149
+ # Get the category (parent) ID
150
+ # @return [Snowflake, nil] Parent category ID
151
+ def category_id
152
+ parent_id if category?
153
+ end
154
+
155
+ # Check if this is a stage channel
156
+ # @return [Boolean] True if stage
157
+ def stage?
158
+ type == 13
159
+ end
160
+
161
+ # Get video quality mode name
162
+ # @return [String] Video quality mode
163
+ def video_quality_mode_name
164
+ video_quality_mode == 2 ? 'full' : 'auto'
165
+ end
166
+
167
+ # Get auto archive duration in minutes
168
+ # @return [Integer, nil] Auto archive duration
169
+ def auto_archive_duration
170
+ default_auto_archive_duration
171
+ end
172
+
173
+ # Check if thread is archived
174
+ # @return [Boolean, nil] True if archived
175
+ def archived?
176
+ thread_metadata&.dig('archived')
177
+ end
178
+
179
+ # Get thread archive timestamp
180
+ # @return [Time, nil] Archive timestamp
181
+ def archive_timestamp
182
+ ts = thread_metadata&.dig('archive_timestamp')
183
+ Time.parse(ts) if ts
184
+ end
185
+
186
+ # Check if thread is locked
187
+ # @return [Boolean, nil] True if locked
188
+ def locked?
189
+ thread_metadata&.dig('locked')
190
+ end
191
+
192
+ # Check if thread is invitable
193
+ # @return [Boolean, nil] True if invitable
194
+ def invitable?
195
+ thread_metadata&.dig('invitable')
196
+ end
197
+
198
+ # Get created_at from snowflake
199
+ # @return [Time] Channel creation time
200
+ def created_at
201
+ id.timestamp
202
+ end
203
+
204
+ # Get permissions as Permission object
205
+ # @return [Permission, nil] Permissions
206
+ def permission_overwrites
207
+ return nil unless permissions
208
+
209
+ Permission.new(permissions.to_i)
210
+ end
211
+
212
+ # Check if this is a directory channel
213
+ # @return [Boolean] True if directory
214
+ def directory?
215
+ type == 14
216
+ end
217
+
218
+ # Get slowmode delay in seconds
219
+ # @return [Integer] Rate limit per user
220
+ def slowmode_delay
221
+ rate_limit_per_user || 0
222
+ end
223
+
224
+ # Check if slowmode is enabled
225
+ # @return [Boolean] True if slowmode enabled
226
+ def slowmode?
227
+ rate_limit_per_user.to_i > 0
228
+ end
229
+
230
+ # Check if channel is synced with category permissions
231
+ # @return [Boolean, nil] True if synced
232
+ def synced?
233
+ @raw_data['parent_id'] && @raw_data['permission_overwrites']&.empty?
234
+ end
235
+
236
+ # Get mention string for the channel with type indicator
237
+ # @return [String] Channel mention with '#' prefix for text channels
238
+ def mention_with_prefix
239
+ text? ? "<##{id}>" : mention
240
+ end
241
+
242
+ # Get formatted name with type indicator
243
+ # @return [String] Formatted name
244
+ def display_name
245
+ case type
246
+ when 0, 5 then "# #{name}"
247
+ when 2, 13 then "🔊 #{name}"
248
+ when 4 then "📁 #{name}"
249
+ else name
250
+ end
251
+ end
252
+
253
+ # Check if the channel is considered active (has recent messages)
254
+ # @return [Boolean] True if active
255
+ def active?
256
+ return false unless last_message_id
257
+ last_message_at > Time.now - 86400 # Active within last 24 hours
258
+ end
259
+
260
+ # Get the age of the last message
261
+ # @return [Float, nil] Seconds since last message
262
+ def last_message_age
263
+ return nil unless last_message_at
264
+ Time.now - last_message_at
265
+ end
266
+
267
+ # Check if this is a news/announcement channel
268
+ # @return [Boolean] True if news channel
269
+ def news?
270
+ type == 5
271
+ end
272
+
273
+ # Get the default auto archive duration in days
274
+ # @return [Integer] Days
275
+ def auto_archive_days
276
+ (default_auto_archive_duration || 4320) / 1440 # Convert minutes to days, default 3 days
277
+ end
278
+
279
+ # Fetch messages from the channel with pagination support
280
+ # @param limit [Integer] Number of messages (1-100, default 50)
281
+ # @param before [String, Snowflake] Get messages before this message ID
282
+ # @param after [String, Snowflake] Get messages after this message ID
283
+ # @param around [String, Snowflake] Get messages around this message ID (returns 25 before + 25 after)
284
+ # @return [Array<Message>] Messages
285
+ def fetch_messages(limit: 50, before: nil, after: nil, around: nil)
286
+ return [] unless self.class.api
287
+
288
+ params = { limit: limit }
289
+ params[:before] = before.to_s if before
290
+ params[:after] = after.to_s if after
291
+ params[:around] = around.to_s if around
292
+
293
+ data = self.class.api.get("/channels/#{id}/messages", params: params)
294
+ data.map { |m| Message.new(m) }
295
+ end
296
+
297
+ # Fetch all messages from the channel with automatic pagination
298
+ # @param max [Integer] Maximum messages to fetch (nil for all)
299
+ # @param batch_size [Integer] Messages per request (1-100)
300
+ # @param direction [Symbol] :backwards (older first) or :forwards (newer first)
301
+ # @yield [Message] Optional block called for each message
302
+ # @return [Array<Message>] All fetched messages
303
+ def fetch_all_messages(max: nil, batch_size: 100, direction: :backwards)
304
+ return [] unless self.class.api
305
+
306
+ messages = []
307
+ last_id = nil
308
+
309
+ loop do
310
+ batch = if direction == :forwards
311
+ fetch_messages(limit: batch_size, after: last_id)
312
+ else
313
+ fetch_messages(limit: batch_size, before: last_id)
314
+ end
315
+
316
+ break if batch.empty?
317
+
318
+ batch.each do |message|
319
+ messages << message
320
+ yield message if block_given?
321
+
322
+ return messages if max && messages.length >= max
323
+ end
324
+
325
+ last_id = direction == :forwards ? batch.last.id : batch.last.id
326
+
327
+ # Stop if we got fewer messages than requested (reached the end)
328
+ break if batch.length < batch_size
329
+ end
330
+
331
+ messages
332
+ end
333
+
334
+ # Create an iterator for fetching messages
335
+ # @param batch_size [Integer] Messages per request (1-100)
336
+ # @param direction [Symbol] :backwards (older first) or :forwards (newer first)
337
+ # @return [MessageIterator] Iterator instance
338
+ def messages_iterator(batch_size: 100, direction: :backwards)
339
+ MessageIterator.new(self, batch_size: batch_size, direction: direction)
340
+ end
341
+
342
+ # Search for messages by content (client-side filtering)
343
+ # Note: Discord API doesn't support server-side message search, this fetches and filters
344
+ # @param content [String] Content to search for
345
+ # @param author_id [String] Filter by author ID
346
+ # @param limit [Integer] Maximum messages to search
347
+ # @return [Array<Message>] Matching messages
348
+ def search_messages(content: nil, author_id: nil, limit: 1000)
349
+ return [] unless self.class.api
350
+
351
+ results = []
352
+
353
+ fetch_all_messages(max: limit) do |message|
354
+ next if content && !message.content.to_s.downcase.include?(content.downcase)
355
+ next if author_id && message.author&.id.to_s != author_id.to_s
356
+
357
+ results << message
358
+ end
359
+
360
+ results
361
+ end
362
+ end
363
+
364
+ # Iterator for paginating through channel messages
365
+ class MessageIterator
366
+ include Enumerable
367
+
368
+ # @return [Channel] Channel being iterated
369
+ attr_reader :channel
370
+
371
+ # @return [Integer] Messages per batch
372
+ attr_reader :batch_size
373
+
374
+ # @return [Symbol] Direction (:backwards or :forwards)
375
+ attr_reader :direction
376
+
377
+ # Initialize iterator
378
+ # @param channel [Channel] Channel to iterate
379
+ # @param batch_size [Integer] Messages per batch
380
+ # @param direction [Symbol] :backwards (older first) or :forwards (newer first)
381
+ def initialize(channel, batch_size: 100, direction: :backwards)
382
+ @channel = channel
383
+ @batch_size = batch_size
384
+ @direction = direction
385
+ @last_id = nil
386
+ @buffer = []
387
+ @exhausted = false
388
+ end
389
+
390
+ # Get next message
391
+ # @return [Message, nil] Next message or nil if exhausted
392
+ def next
393
+ fill_buffer if @buffer.empty? && !@exhausted
394
+
395
+ @buffer.shift
396
+ end
397
+
398
+ # Check if there are more messages
399
+ # @return [Boolean] True if more messages available
400
+ def more?
401
+ !@exhausted || !@buffer.empty?
402
+ end
403
+
404
+ # Iterate over all messages
405
+ # @yield [Message]
406
+ def each
407
+ return to_enum unless block_given?
408
+
409
+ loop do
410
+ message = self.next
411
+ break unless message
412
+
413
+ yield message
414
+ end
415
+ end
416
+
417
+ # Reset the iterator
418
+ # @return [self]
419
+ def reset
420
+ @last_id = nil
421
+ @buffer = []
422
+ @exhausted = false
423
+ self
424
+ end
425
+
426
+ private
427
+
428
+ def fill_buffer
429
+ return if @exhausted
430
+
431
+ batch = if @direction == :forwards
432
+ @channel.fetch_messages(limit: @batch_size, after: @last_id)
433
+ else
434
+ @channel.fetch_messages(limit: @batch_size, before: @last_id)
435
+ end
436
+
437
+ if batch.empty?
438
+ @exhausted = true
439
+ else
440
+ @buffer = batch
441
+ @last_id = @direction == :forwards ? batch.last.id : batch.last.id
442
+ @exhausted = true if batch.length < @batch_size
443
+ end
444
+ end
445
+ end
446
+ end
@@ -0,0 +1,280 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DiscordRDA
4
+ # DSL for building channel modifications.
5
+ # Provides a fluent interface for modifying channel properties.
6
+ #
7
+ class ChannelBuilder
8
+ # @return [Hash] Channel data being built
9
+ attr_reader :data
10
+
11
+ # Initialize a new channel builder
12
+ # @param name [String] Channel name
13
+ def initialize(name = nil)
14
+ @data = {}
15
+ @data[:name] = name if name
16
+ end
17
+
18
+ # Set channel name
19
+ # @param name [String] Channel name (1-100 characters)
20
+ # @return [self]
21
+ def name(name)
22
+ @data[:name] = name
23
+ self
24
+ end
25
+
26
+ # Set channel type
27
+ # @param type [Integer, Symbol] Channel type (0=guild_text, 2=guild_voice, etc.)
28
+ # @return [self]
29
+ def type(type)
30
+ @data[:type] = type.is_a?(Symbol) ? Channel::TYPES[type] : type
31
+ self
32
+ end
33
+
34
+ # Set channel topic
35
+ # @param topic [String] Channel topic (0-1024 characters for text, 0-4096 for forum)
36
+ # @return [self]
37
+ def topic(topic)
38
+ @data[:topic] = topic
39
+ self
40
+ end
41
+
42
+ # Set channel bitrate (for voice channels)
43
+ # @param bitrate [Integer] Bitrate in bits (8000-384000, or 8000-128000 for stage)
44
+ # @return [self]
45
+ def bitrate(bitrate)
46
+ @data[:bitrate] = bitrate
47
+ self
48
+ end
49
+
50
+ # Set user limit (for voice channels)
51
+ # @param limit [Integer] User limit (0-99, 0 = unlimited)
52
+ # @return [self]
53
+ def user_limit(limit)
54
+ @data[:user_limit] = limit
55
+ self
56
+ end
57
+
58
+ # Set rate limit per user (slowmode)
59
+ # @param seconds [Integer] Seconds between messages (0-21600)
60
+ # @return [self]
61
+ def slowmode(seconds)
62
+ @data[:rate_limit_per_user] = seconds
63
+ self
64
+ end
65
+
66
+ # Set channel position
67
+ # @param position [Integer] Position in the left-hand listing
68
+ # @return [self]
69
+ def position(position)
70
+ @data[:position] = position
71
+ self
72
+ end
73
+
74
+ # Set parent category ID
75
+ # @param category_id [String, Snowflake] Parent category ID
76
+ # @return [self]
77
+ def parent(category_id)
78
+ @data[:parent_id] = category_id.to_s
79
+ self
80
+ end
81
+
82
+ # Set NSFW flag
83
+ # @param nsfw [Boolean] Whether the channel is NSFW
84
+ # @return [self]
85
+ def nsfw(nsfw = true)
86
+ @data[:nsfw] = nsfw
87
+ self
88
+ end
89
+
90
+ # Set default auto archive duration (for threads)
91
+ # @param minutes [Integer] Duration in minutes (60, 1440, 4320, 10080)
92
+ # @return [self]
93
+ def default_auto_archive_duration(minutes)
94
+ @data[:default_auto_archive_duration] = minutes
95
+ self
96
+ end
97
+
98
+ # Set default thread rate limit per user
99
+ # @param seconds [Integer] Seconds between messages in threads
100
+ # @return [self]
101
+ def default_thread_slowmode(seconds)
102
+ @data[:default_thread_rate_limit_per_user] = seconds
103
+ self
104
+ end
105
+
106
+ # Set permission overwrites
107
+ # @param overwrites [Array<Hash>] Permission overwrites array
108
+ # @return [self]
109
+ def permission_overwrites(overwrites)
110
+ @data[:permission_overwrites] = overwrites.map { |o| normalize_overwrite(o) }
111
+ self
112
+ end
113
+
114
+ # Add a single permission overwrite
115
+ # @param id [String, Snowflake] Role or user ID
116
+ # @param type [Integer] 0 for role, 1 for member
117
+ # @param allow [Integer, String] Allowed permissions bitfield
118
+ # @param deny [Integer, String] Denied permissions bitfield
119
+ # @return [self]
120
+ def add_overwrite(id:, type:, allow: 0, deny: 0)
121
+ @data[:permission_overwrites] ||= []
122
+ @data[:permission_overwrites] << {
123
+ id: id.to_s,
124
+ type: type,
125
+ allow: allow.is_a?(Integer) ? allow.to_s : allow,
126
+ deny: deny.is_a?(Integer) ? deny.to_s : deny
127
+ }
128
+ self
129
+ end
130
+
131
+ # Set channel flags
132
+ # @param flags [Integer] Channel flags bitfield
133
+ # @return [self]
134
+ def flags(flags)
135
+ @data[:flags] = flags
136
+ self
137
+ end
138
+
139
+ # Set available tags (for forum channels)
140
+ # @param tags [Array<Hash>] Available tags
141
+ # @return [self]
142
+ def available_tags(tags)
143
+ @data[:available_tags] = tags
144
+ self
145
+ end
146
+
147
+ # Set default sort order (for forum channels)
148
+ # @param order [Integer] Default sort order type
149
+ # @return [self]
150
+ def default_sort_order(order)
151
+ @data[:default_sort_order] = order
152
+ self
153
+ end
154
+
155
+ # Set default forum layout (for forum channels)
156
+ # @param layout [Integer] Default forum layout view
157
+ # @return [self]
158
+ def default_forum_layout(layout)
159
+ @data[:default_forum_layout] = layout
160
+ self
161
+ end
162
+
163
+ # Set default reaction emoji (for forum channels)
164
+ # @param emoji [Hash] Default emoji for forum posts
165
+ # @return [self]
166
+ def default_reaction_emoji(emoji)
167
+ @data[:default_reaction_emoji] = emoji
168
+ self
169
+ end
170
+
171
+ # Set video quality mode (for voice channels)
172
+ # @param mode [Integer] Video quality mode (1=auto, 2=full)
173
+ # @return [self]
174
+ def video_quality_mode(mode)
175
+ @data[:video_quality_mode] = mode
176
+ self
177
+ end
178
+
179
+ # Convert builder to hash
180
+ # @return [Hash] Channel data hash
181
+ def to_h
182
+ @data.dup
183
+ end
184
+
185
+ # Build and return the channel data
186
+ # @return [Hash] Channel data
187
+ def build
188
+ to_h
189
+ end
190
+
191
+ private
192
+
193
+ def normalize_overwrite(overwrite)
194
+ {
195
+ id: overwrite[:id] || overwrite['id'],
196
+ type: overwrite[:type] || overwrite['type'],
197
+ allow: (overwrite[:allow] || overwrite['allow'] || 0).to_s,
198
+ deny: (overwrite[:deny] || overwrite['deny'] || 0).to_s
199
+ }
200
+ end
201
+ end
202
+
203
+ # DSL for creating channel invites
204
+ class InviteBuilder
205
+ # @return [Hash] Invite data being built
206
+ attr_reader :data
207
+
208
+ def initialize
209
+ @data = {}
210
+ end
211
+
212
+ # Set max age in seconds
213
+ # @param seconds [Integer] Max age (0 = never expires, max 604800)
214
+ # @return [self]
215
+ def max_age(seconds)
216
+ @data[:max_age] = seconds
217
+ self
218
+ end
219
+
220
+ # Set max uses
221
+ # @param uses [Integer] Max uses (0 = unlimited, max 100)
222
+ # @return [self]
223
+ def max_uses(uses)
224
+ @data[:max_uses] = uses
225
+ self
226
+ end
227
+
228
+ # Set temporary membership
229
+ # @param temporary [Boolean] Whether invite grants temporary membership
230
+ # @return [self]
231
+ def temporary(temporary = true)
232
+ @data[:temporary] = temporary
233
+ self
234
+ end
235
+
236
+ # Set unique invite
237
+ # @param unique [Boolean] Whether to create unique URL
238
+ # @return [self]
239
+ def unique(unique = true)
240
+ @data[:unique] = unique
241
+ self
242
+ end
243
+
244
+ # Set target type
245
+ # @param type [Integer] Target type (1=stream, 2=embedded_application)
246
+ # @return [self]
247
+ def target_type(type)
248
+ @data[:target_type] = type
249
+ self
250
+ end
251
+
252
+ # Set target user ID
253
+ # @param user_id [String, Snowflake] Target user ID
254
+ # @return [self]
255
+ def target_user(user_id)
256
+ @data[:target_user_id] = user_id.to_s
257
+ self
258
+ end
259
+
260
+ # Set target application ID
261
+ # @param app_id [String, Snowflake] Target application ID
262
+ # @return [self]
263
+ def target_application(app_id)
264
+ @data[:target_application_id] = app_id.to_s
265
+ self
266
+ end
267
+
268
+ # Convert to hash
269
+ # @return [Hash] Invite data
270
+ def to_h
271
+ @data.dup
272
+ end
273
+
274
+ # Build and return invite data
275
+ # @return [Hash] Invite data
276
+ def build
277
+ to_h
278
+ end
279
+ end
280
+ end