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,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
|