discord_rda 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +63 -0
- data/lib/discord_rda/bot.rb +1223 -61
- data/lib/discord_rda/cache/store.rb +2 -0
- data/lib/discord_rda/connection/gateway_client.rb +12 -2
- data/lib/discord_rda/connection/rest_client.rb +10 -1
- data/lib/discord_rda/connection/rest_proxy.rb +5 -1
- data/lib/discord_rda/connection/shard_manager.rb +10 -1
- data/lib/discord_rda/core/configuration.rb +65 -2
- data/lib/discord_rda/core/error_tracker.rb +22 -0
- data/lib/discord_rda/core/execution_supervisor.rb +180 -0
- data/lib/discord_rda/core/logger.rb +17 -3
- data/lib/discord_rda/core/restart_manager.rb +83 -0
- data/lib/discord_rda/core/secrets.rb +30 -0
- data/lib/discord_rda/core/snowflake.rb +1 -1
- data/lib/discord_rda/core/tracer.rb +41 -0
- data/lib/discord_rda/entity/base.rb +10 -3
- data/lib/discord_rda/entity/message_builder.rb +2 -0
- data/lib/discord_rda/entity/support.rb +287 -0
- data/lib/discord_rda/event/base.rb +258 -7
- data/lib/discord_rda/interactions/application_command.rb +44 -6
- data/lib/discord_rda/interactions/interaction.rb +12 -0
- data/lib/discord_rda/persistence/active_record.rb +72 -0
- data/lib/discord_rda/plugin/analytics_plugin.rb +64 -2
- data/lib/discord_rda/version.rb +1 -1
- data/lib/discord_rda.rb +6 -0
- metadata +64 -2
data/lib/discord_rda/bot.rb
CHANGED
|
@@ -40,6 +40,24 @@ module DiscordRDA
|
|
|
40
40
|
# @return [PluginRegistry] Plugin registry
|
|
41
41
|
attr_reader :plugins
|
|
42
42
|
|
|
43
|
+
# @return [Tracer] Trace helper
|
|
44
|
+
attr_reader :tracer
|
|
45
|
+
|
|
46
|
+
# @return [ErrorTracker] Error tracking helper
|
|
47
|
+
attr_reader :error_tracker
|
|
48
|
+
|
|
49
|
+
# @return [RestartManager] Instant restart helper
|
|
50
|
+
attr_reader :restart_manager
|
|
51
|
+
|
|
52
|
+
# @return [ExecutionSupervisor] Fault-tolerant execution supervisor
|
|
53
|
+
attr_reader :supervisor
|
|
54
|
+
|
|
55
|
+
# @return [Hash] Boot-time restart state
|
|
56
|
+
attr_reader :restart_state
|
|
57
|
+
|
|
58
|
+
# @return [ActiveRecordSystem, nil] ActiveRecord integration helper
|
|
59
|
+
attr_reader :active_record
|
|
60
|
+
|
|
43
61
|
# @return [Boolean] Whether bot is running
|
|
44
62
|
attr_reader :running
|
|
45
63
|
|
|
@@ -51,15 +69,25 @@ module DiscordRDA
|
|
|
51
69
|
# @param options [Hash] Configuration options
|
|
52
70
|
def initialize(token:, **options)
|
|
53
71
|
@config = Configuration.new(options.merge(token: token))
|
|
54
|
-
@logger = Logger.new(
|
|
72
|
+
@logger = Logger.new(
|
|
73
|
+
level: @config.log_level,
|
|
74
|
+
format: @config.log_format,
|
|
75
|
+
file_path: @config.log_file_path,
|
|
76
|
+
rotate_age: @config.log_rotate_age,
|
|
77
|
+
rotate_size: @config.log_rotate_size
|
|
78
|
+
)
|
|
79
|
+
@restart_manager = RestartManager.new(logger: @logger)
|
|
80
|
+
@restart_state = @restart_manager.consume_boot_state
|
|
81
|
+
@tracer = Tracer.new(enabled: @config.trace_enabled, logger: @logger)
|
|
82
|
+
@error_tracker = ErrorTracker.new(enabled: @config.error_tracking, logger: @logger)
|
|
83
|
+
@supervisor = ExecutionSupervisor.new(logger: @logger)
|
|
55
84
|
@event_bus = EventBus.new(logger: @logger)
|
|
56
85
|
@cache = build_cache
|
|
57
|
-
@shard_manager = ShardManager.new(@config, @event_bus, @logger)
|
|
86
|
+
@shard_manager = ShardManager.new(@config, @event_bus, @logger, gateway_state: restart_gateway_state)
|
|
58
87
|
@rest = RestClient.new(@config, @logger)
|
|
59
88
|
|
|
60
89
|
# Configure entity API clients
|
|
61
|
-
|
|
62
|
-
Interaction.api = @rest
|
|
90
|
+
configure_entity_apis(@rest)
|
|
63
91
|
|
|
64
92
|
setup_event_handlers
|
|
65
93
|
setup_interaction_handlers
|
|
@@ -69,13 +97,25 @@ module DiscordRDA
|
|
|
69
97
|
@reshard_manager = ReshardManager.new(self, @shard_manager, @logger)
|
|
70
98
|
@hot_reload_manager = HotReloadManager.new(self, @logger)
|
|
71
99
|
@plugins = PluginRegistry.new(logger: @logger)
|
|
100
|
+
@restart_manager.attach(self)
|
|
72
101
|
@slash_commands = {}
|
|
73
102
|
@running = false
|
|
74
103
|
@commands = {}
|
|
104
|
+
@active_record = nil
|
|
75
105
|
|
|
76
106
|
setup_event_handlers
|
|
77
107
|
end
|
|
78
108
|
|
|
109
|
+
def restart!(command: nil, env: {})
|
|
110
|
+
@restart_manager.restart!(command: command, env: env)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def enable_active_record(database_url: nil, **options)
|
|
114
|
+
@active_record = ActiveRecordSystem.new(logger: @logger)
|
|
115
|
+
@active_record.connect(database_url: database_url, **options)
|
|
116
|
+
@active_record
|
|
117
|
+
end
|
|
118
|
+
|
|
79
119
|
# Register a slash command (global or guild-specific)
|
|
80
120
|
# @param name [String] Command name
|
|
81
121
|
# @param description [String] Command description
|
|
@@ -87,31 +127,14 @@ module DiscordRDA
|
|
|
87
127
|
# @yield [CommandBuilder] DSL block for building command
|
|
88
128
|
# @return [ApplicationCommand] Registered command
|
|
89
129
|
def slash(name, description, **options, &block)
|
|
90
|
-
builder = CommandBuilder.new(name, description)
|
|
130
|
+
builder = CommandBuilder.new(name, description, type: options[:type] || 1)
|
|
91
131
|
builder.dm_allowed(options[:dm_permission]) if options.key?(:dm_permission)
|
|
92
132
|
builder.default_permissions(options[:default_member_permissions]) if options[:default_member_permissions]
|
|
93
133
|
builder.nsfw(options[:nsfw]) if options[:nsfw]
|
|
94
134
|
|
|
95
135
|
block.call(builder) if block
|
|
96
136
|
|
|
97
|
-
|
|
98
|
-
cmd.instance_variable_set(:@application_id, me.id.to_s) rescue nil
|
|
99
|
-
cmd.instance_variable_set(:@guild_id, options[:guild_id].to_s) if options[:guild_id]
|
|
100
|
-
|
|
101
|
-
key = options[:guild_id] ? "#{name}:#{options[:guild_id]}" : name
|
|
102
|
-
@slash_commands[key] = cmd
|
|
103
|
-
|
|
104
|
-
# Register with Discord if we have application ID
|
|
105
|
-
if cmd.application_id
|
|
106
|
-
if options[:guild_id]
|
|
107
|
-
cmd.create_guild(self, options[:guild_id])
|
|
108
|
-
else
|
|
109
|
-
cmd.create_global(self)
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
@logger.info('Registered slash command', name: name, guild: options[:guild_id] || 'global')
|
|
114
|
-
cmd
|
|
137
|
+
register_application_command(builder.build, name: name, guild_id: options[:guild_id])
|
|
115
138
|
end
|
|
116
139
|
|
|
117
140
|
# Register a context menu command (user or message)
|
|
@@ -122,10 +145,13 @@ module DiscordRDA
|
|
|
122
145
|
# @return [ApplicationCommand] Registered command
|
|
123
146
|
def context_menu(type:, name:, **options, &block)
|
|
124
147
|
cmd_type = type == :user ? 2 : 3
|
|
125
|
-
|
|
126
|
-
options[:
|
|
148
|
+
builder = CommandBuilder.new(name, '', type: cmd_type)
|
|
149
|
+
builder.dm_allowed(options[:dm_permission]) if options.key?(:dm_permission)
|
|
150
|
+
builder.default_permissions(options[:default_member_permissions]) if options[:default_member_permissions]
|
|
151
|
+
builder.nsfw(options[:nsfw]) if options[:nsfw]
|
|
152
|
+
builder.handler(&block) if block
|
|
127
153
|
|
|
128
|
-
|
|
154
|
+
register_application_command(builder.build, name: name, guild_id: options[:guild_id])
|
|
129
155
|
end
|
|
130
156
|
|
|
131
157
|
# Bulk register global commands (replaces existing)
|
|
@@ -216,6 +242,7 @@ module DiscordRDA
|
|
|
216
242
|
# @return [void]
|
|
217
243
|
def run(async: false)
|
|
218
244
|
@running = true
|
|
245
|
+
install_signal_handlers
|
|
219
246
|
|
|
220
247
|
@logger.info('Starting DiscordRDA bot', version: VERSION, shards: @config.shards.length)
|
|
221
248
|
|
|
@@ -291,6 +318,33 @@ module DiscordRDA
|
|
|
291
318
|
nil
|
|
292
319
|
end
|
|
293
320
|
|
|
321
|
+
# Create a guild
|
|
322
|
+
# @param name [String] Guild name
|
|
323
|
+
# @param options [Hash] Optional guild creation payload
|
|
324
|
+
# @return [Guild] Created guild
|
|
325
|
+
def create_guild(name:, **options)
|
|
326
|
+
payload = { name: name }.merge(options).compact
|
|
327
|
+
data = @rest.post('/guilds', body: payload)
|
|
328
|
+
Guild.new(data)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Modify a guild
|
|
332
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
333
|
+
# @param reason [String, nil] Audit log reason
|
|
334
|
+
# @param options [Hash] Guild modification payload
|
|
335
|
+
# @return [Guild] Updated guild
|
|
336
|
+
def modify_guild(guild_id, reason: nil, **options)
|
|
337
|
+
data = @rest.patch("/guilds/#{guild_id}", body: options.compact, headers: audit_log_headers(reason))
|
|
338
|
+
Guild.new(data)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Delete a guild
|
|
342
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
343
|
+
# @return [void]
|
|
344
|
+
def delete_guild(guild_id)
|
|
345
|
+
@rest.delete("/guilds/#{guild_id}")
|
|
346
|
+
end
|
|
347
|
+
|
|
294
348
|
# Get a channel by ID
|
|
295
349
|
# @param channel_id [String, Snowflake] Channel ID
|
|
296
350
|
# @return [Channel, nil] Channel or nil
|
|
@@ -317,7 +371,16 @@ module DiscordRDA
|
|
|
317
371
|
Message.new(data)
|
|
318
372
|
end
|
|
319
373
|
|
|
320
|
-
#
|
|
374
|
+
# Crosspost a message in an announcement channel
|
|
375
|
+
# @param channel_id [String, Snowflake] Channel ID
|
|
376
|
+
# @param message_id [String, Snowflake] Message ID
|
|
377
|
+
# @return [Message] Crossposted message
|
|
378
|
+
def crosspost_message(channel_id, message_id)
|
|
379
|
+
data = @rest.post("/channels/#{channel_id}/messages/#{message_id}/crosspost")
|
|
380
|
+
Message.new(data)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Get messages from a channel with pagination
|
|
321
384
|
# @param channel_id [String, Snowflake] Channel ID
|
|
322
385
|
# @param limit [Integer] Max messages to fetch (1-100, default 50)
|
|
323
386
|
# @param before [String, Snowflake] Get messages before this ID
|
|
@@ -345,6 +408,226 @@ module DiscordRDA
|
|
|
345
408
|
nil
|
|
346
409
|
end
|
|
347
410
|
|
|
411
|
+
# Add a recipient to a group DM
|
|
412
|
+
# @param channel_id [String, Snowflake] Group DM channel ID
|
|
413
|
+
# @param user_id [String, Snowflake] User ID
|
|
414
|
+
# @param access_token [String] OAuth2 access token with gdm.join scope
|
|
415
|
+
# @param nick [String, nil] Nickname for the recipient in the group DM
|
|
416
|
+
# @return [void]
|
|
417
|
+
def add_group_dm_recipient(channel_id, user_id, access_token:, nick: nil)
|
|
418
|
+
payload = { access_token: access_token, nick: nick }.compact
|
|
419
|
+
@rest.put("/channels/#{channel_id}/recipients/#{user_id}", body: payload)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# Remove a recipient from a group DM
|
|
423
|
+
# @param channel_id [String, Snowflake] Group DM channel ID
|
|
424
|
+
# @param user_id [String, Snowflake] User ID
|
|
425
|
+
# @return [void]
|
|
426
|
+
def remove_group_dm_recipient(channel_id, user_id)
|
|
427
|
+
@rest.delete("/channels/#{channel_id}/recipients/#{user_id}")
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Trigger typing indicator for a channel
|
|
431
|
+
# @param channel_id [String, Snowflake] Channel ID
|
|
432
|
+
# @return [void]
|
|
433
|
+
def trigger_typing(channel_id)
|
|
434
|
+
@rest.post("/channels/#{channel_id}/typing")
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# Get pinned messages for a channel
|
|
438
|
+
# @param channel_id [String, Snowflake] Channel ID
|
|
439
|
+
# @return [Array<Message>] Pinned messages
|
|
440
|
+
def pinned_messages(channel_id)
|
|
441
|
+
data = @rest.get("/channels/#{channel_id}/pins")
|
|
442
|
+
data.map { |message| Message.new(message) }
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# Pin a channel message
|
|
446
|
+
# @param channel_id [String, Snowflake] Channel ID
|
|
447
|
+
# @param message_id [String, Snowflake] Message ID
|
|
448
|
+
# @param reason [String, nil] Audit log reason
|
|
449
|
+
# @return [void]
|
|
450
|
+
def pin_message(channel_id, message_id, reason: nil)
|
|
451
|
+
@rest.put("/channels/#{channel_id}/pins/#{message_id}", headers: audit_log_headers(reason))
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# Unpin a channel message
|
|
455
|
+
# @param channel_id [String, Snowflake] Channel ID
|
|
456
|
+
# @param message_id [String, Snowflake] Message ID
|
|
457
|
+
# @param reason [String, nil] Audit log reason
|
|
458
|
+
# @return [void]
|
|
459
|
+
def unpin_message(channel_id, message_id, reason: nil)
|
|
460
|
+
@rest.delete("/channels/#{channel_id}/pins/#{message_id}", headers: audit_log_headers(reason))
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# Edit a channel permission overwrite
|
|
464
|
+
# @param channel_id [String, Snowflake] Channel ID
|
|
465
|
+
# @param overwrite_id [String, Snowflake] Role or member ID
|
|
466
|
+
# @param allow [Integer, String] Allowed permissions bitfield
|
|
467
|
+
# @param deny [Integer, String] Denied permissions bitfield
|
|
468
|
+
# @param type [Integer] 0 for role, 1 for member
|
|
469
|
+
# @param reason [String, nil] Audit log reason
|
|
470
|
+
# @return [void]
|
|
471
|
+
def edit_channel_permissions(channel_id, overwrite_id, allow:, deny:, type:, reason: nil)
|
|
472
|
+
payload = {
|
|
473
|
+
allow: allow.to_s,
|
|
474
|
+
deny: deny.to_s,
|
|
475
|
+
type: type
|
|
476
|
+
}
|
|
477
|
+
@rest.put("/channels/#{channel_id}/permissions/#{overwrite_id}", body: payload, headers: audit_log_headers(reason))
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# Delete a channel permission overwrite
|
|
481
|
+
# @param channel_id [String, Snowflake] Channel ID
|
|
482
|
+
# @param overwrite_id [String, Snowflake] Role or member overwrite ID
|
|
483
|
+
# @param reason [String, nil] Audit log reason
|
|
484
|
+
# @return [void]
|
|
485
|
+
def delete_channel_permission(channel_id, overwrite_id, reason: nil)
|
|
486
|
+
@rest.delete("/channels/#{channel_id}/permissions/#{overwrite_id}", headers: audit_log_headers(reason))
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
# Get invites for a channel
|
|
490
|
+
# @param channel_id [String, Snowflake] Channel ID
|
|
491
|
+
# @return [Array<Hash>] Invite payloads
|
|
492
|
+
def channel_invites(channel_id)
|
|
493
|
+
@rest.get("/channels/#{channel_id}/invites")
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# Create a channel invite
|
|
497
|
+
# @param channel_id [String, Snowflake] Channel ID
|
|
498
|
+
# @param reason [String, nil] Audit log reason
|
|
499
|
+
# @yield [InviteBuilder] Optional invite builder block
|
|
500
|
+
# @return [Hash] Invite payload
|
|
501
|
+
def create_channel_invite(channel_id, reason: nil, **options, &block)
|
|
502
|
+
builder = InviteBuilder.new
|
|
503
|
+
options.each do |key, value|
|
|
504
|
+
builder.public_send(key, value) if builder.respond_to?(key)
|
|
505
|
+
end
|
|
506
|
+
block.call(builder) if block
|
|
507
|
+
|
|
508
|
+
@rest.post("/channels/#{channel_id}/invites", body: builder.to_h, headers: audit_log_headers(reason))
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
# Follow an announcement channel into a target channel
|
|
512
|
+
# @param channel_id [String, Snowflake] Source announcement channel ID
|
|
513
|
+
# @param webhook_channel_id [String, Snowflake] Destination channel ID
|
|
514
|
+
# @return [Hash] Followed channel response
|
|
515
|
+
def follow_news_channel(channel_id, webhook_channel_id)
|
|
516
|
+
@rest.post("/channels/#{channel_id}/followers", body: { webhook_channel_id: webhook_channel_id.to_s })
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# Start a thread from an existing message
|
|
520
|
+
# @param channel_id [String, Snowflake] Parent channel ID
|
|
521
|
+
# @param message_id [String, Snowflake] Message ID
|
|
522
|
+
# @param name [String] Thread name
|
|
523
|
+
# @param auto_archive_duration [Integer, nil] Auto archive duration in minutes
|
|
524
|
+
# @param rate_limit_per_user [Integer, nil] Thread slowmode
|
|
525
|
+
# @return [Channel] Created thread
|
|
526
|
+
def start_thread_from_message(channel_id, message_id, name:, auto_archive_duration: nil, rate_limit_per_user: nil)
|
|
527
|
+
payload = {
|
|
528
|
+
name: name,
|
|
529
|
+
auto_archive_duration: auto_archive_duration,
|
|
530
|
+
rate_limit_per_user: rate_limit_per_user
|
|
531
|
+
}.compact
|
|
532
|
+
data = @rest.post("/channels/#{channel_id}/messages/#{message_id}/threads", body: payload)
|
|
533
|
+
Channel.new(data)
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
# Start a thread without a seed message
|
|
537
|
+
# @param channel_id [String, Snowflake] Parent channel ID
|
|
538
|
+
# @param name [String] Thread name
|
|
539
|
+
# @param type [Integer, nil] Thread type
|
|
540
|
+
# @param auto_archive_duration [Integer, nil] Auto archive duration in minutes
|
|
541
|
+
# @param invitable [Boolean, nil] Whether non-moderators can add users
|
|
542
|
+
# @param rate_limit_per_user [Integer, nil] Thread slowmode
|
|
543
|
+
# @return [Channel] Created thread
|
|
544
|
+
def start_thread(channel_id, name:, type: nil, auto_archive_duration: nil, invitable: nil, rate_limit_per_user: nil)
|
|
545
|
+
payload = {
|
|
546
|
+
name: name,
|
|
547
|
+
type: type,
|
|
548
|
+
auto_archive_duration: auto_archive_duration,
|
|
549
|
+
invitable: invitable,
|
|
550
|
+
rate_limit_per_user: rate_limit_per_user
|
|
551
|
+
}.compact
|
|
552
|
+
data = @rest.post("/channels/#{channel_id}/threads", body: payload)
|
|
553
|
+
Channel.new(data)
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# Join a thread
|
|
557
|
+
# @param thread_id [String, Snowflake] Thread channel ID
|
|
558
|
+
# @return [void]
|
|
559
|
+
def join_thread(thread_id)
|
|
560
|
+
@rest.put("/channels/#{thread_id}/thread-members/@me")
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
# Add a user to a thread
|
|
564
|
+
# @param thread_id [String, Snowflake] Thread channel ID
|
|
565
|
+
# @param user_id [String, Snowflake] User ID
|
|
566
|
+
# @return [void]
|
|
567
|
+
def add_thread_member(thread_id, user_id)
|
|
568
|
+
@rest.put("/channels/#{thread_id}/thread-members/#{user_id}")
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# Leave a thread
|
|
572
|
+
# @param thread_id [String, Snowflake] Thread channel ID
|
|
573
|
+
# @return [void]
|
|
574
|
+
def leave_thread(thread_id)
|
|
575
|
+
@rest.delete("/channels/#{thread_id}/thread-members/@me")
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# Remove a user from a thread
|
|
579
|
+
# @param thread_id [String, Snowflake] Thread channel ID
|
|
580
|
+
# @param user_id [String, Snowflake] User ID
|
|
581
|
+
# @return [void]
|
|
582
|
+
def remove_thread_member(thread_id, user_id)
|
|
583
|
+
@rest.delete("/channels/#{thread_id}/thread-members/#{user_id}")
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
# Get a specific thread member
|
|
587
|
+
# @param thread_id [String, Snowflake] Thread channel ID
|
|
588
|
+
# @param user_id [String, Snowflake] User ID
|
|
589
|
+
# @param with_member [Boolean] Include member object when available
|
|
590
|
+
# @return [Hash, nil] Thread member payload
|
|
591
|
+
def thread_member(thread_id, user_id, with_member: false)
|
|
592
|
+
@rest.get("/channels/#{thread_id}/thread-members/#{user_id}", params: { with_member: with_member })
|
|
593
|
+
rescue RestClient::NotFoundError
|
|
594
|
+
nil
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# List members in a thread
|
|
598
|
+
# @param thread_id [String, Snowflake] Thread channel ID
|
|
599
|
+
# @param with_member [Boolean] Include member objects when available
|
|
600
|
+
# @param after [String, Snowflake, nil] Cursor
|
|
601
|
+
# @param limit [Integer, nil] Max results
|
|
602
|
+
# @return [Array<Hash>] Thread member payloads
|
|
603
|
+
def thread_members(thread_id, with_member: false, after: nil, limit: nil)
|
|
604
|
+
params = { with_member: with_member }
|
|
605
|
+
params[:after] = after.to_s if after
|
|
606
|
+
params[:limit] = limit if limit
|
|
607
|
+
@rest.get("/channels/#{thread_id}/thread-members", params: params)
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# List archived threads for a channel
|
|
611
|
+
# @param channel_id [String, Snowflake] Parent channel ID
|
|
612
|
+
# @param scope [Symbol] :public, :private, or :joined_private
|
|
613
|
+
# @param before [Time, String, nil] ISO8601 timestamp cursor
|
|
614
|
+
# @param limit [Integer, nil] Max threads to return
|
|
615
|
+
# @return [Hash] Archived thread response
|
|
616
|
+
def archived_threads(channel_id, scope: :public, before: nil, limit: nil)
|
|
617
|
+
path = case scope
|
|
618
|
+
when :public then "/channels/#{channel_id}/threads/archived/public"
|
|
619
|
+
when :private then "/channels/#{channel_id}/threads/archived/private"
|
|
620
|
+
when :joined_private then "/channels/#{channel_id}/users/@me/threads/archived/private"
|
|
621
|
+
else
|
|
622
|
+
raise ArgumentError, "Unknown archived thread scope: #{scope}"
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
params = {}
|
|
626
|
+
params[:before] = before.is_a?(Time) ? before.iso8601 : before if before
|
|
627
|
+
params[:limit] = limit if limit
|
|
628
|
+
@rest.get(path, params: params)
|
|
629
|
+
end
|
|
630
|
+
|
|
348
631
|
# Enable scalable REST client (queue-based rate limiting)
|
|
349
632
|
# @param proxy [Hash] Optional proxy configuration
|
|
350
633
|
# @return [void]
|
|
@@ -352,6 +635,7 @@ module DiscordRDA
|
|
|
352
635
|
@logger.info('Enabling scalable REST client')
|
|
353
636
|
@scalable_rest = ScalableRestClient.new(@config, @logger, proxy: proxy)
|
|
354
637
|
@scalable_rest.start
|
|
638
|
+
configure_entity_apis(@scalable_rest)
|
|
355
639
|
end
|
|
356
640
|
|
|
357
641
|
# Enable hot reload for development
|
|
@@ -394,7 +678,7 @@ module DiscordRDA
|
|
|
394
678
|
analytics_plugin&.summary || {}
|
|
395
679
|
end
|
|
396
680
|
|
|
397
|
-
# === Message Reactions
|
|
681
|
+
# === Message Reactions ===
|
|
398
682
|
|
|
399
683
|
# Add a reaction to a message
|
|
400
684
|
# @param channel_id [String, Snowflake] Channel ID
|
|
@@ -417,15 +701,18 @@ module DiscordRDA
|
|
|
417
701
|
@rest.delete("/channels/#{channel_id}/messages/#{message_id}/reactions/#{CGI.escape(emoji_str)}/#{user_id}")
|
|
418
702
|
end
|
|
419
703
|
|
|
420
|
-
# Get reactions for a message
|
|
704
|
+
# Get reactions for a message
|
|
421
705
|
# @param channel_id [String, Snowflake] Channel ID
|
|
422
706
|
# @param message_id [String, Snowflake] Message ID
|
|
423
707
|
# @param emoji [String, Emoji] Emoji filter
|
|
424
708
|
# @param limit [Integer] Max users to return (1-100, default 25)
|
|
709
|
+
# @param after [String, Snowflake, nil] Cursor for pagination
|
|
425
710
|
# @return [Array<User>] Users who reacted
|
|
426
|
-
def get_reactions(channel_id, message_id, emoji, limit: 25)
|
|
711
|
+
def get_reactions(channel_id, message_id, emoji, limit: 25, after: nil)
|
|
427
712
|
emoji_str = emoji.respond_to?(:id) ? "#{emoji.name}:#{emoji.id}" : emoji.to_s
|
|
428
|
-
|
|
713
|
+
params = { limit: limit }
|
|
714
|
+
params[:after] = after.to_s if after
|
|
715
|
+
data = @rest.get("/channels/#{channel_id}/messages/#{message_id}/reactions/#{CGI.escape(emoji_str)}", params: params)
|
|
429
716
|
data.map { |u| User.new(u) }
|
|
430
717
|
end
|
|
431
718
|
|
|
@@ -437,7 +724,7 @@ module DiscordRDA
|
|
|
437
724
|
@rest.delete("/channels/#{channel_id}/messages/#{message_id}/reactions")
|
|
438
725
|
end
|
|
439
726
|
|
|
440
|
-
# === Guild Members
|
|
727
|
+
# === Guild Members ===
|
|
441
728
|
|
|
442
729
|
# Get a guild member
|
|
443
730
|
# @param guild_id [String, Snowflake] Guild ID
|
|
@@ -450,7 +737,7 @@ module DiscordRDA
|
|
|
450
737
|
nil
|
|
451
738
|
end
|
|
452
739
|
|
|
453
|
-
# List guild members
|
|
740
|
+
# List guild members with pagination
|
|
454
741
|
# @param guild_id [String, Snowflake] Guild ID
|
|
455
742
|
# @param limit [Integer] Max members (1-1000, default 100)
|
|
456
743
|
# @param after [String, Snowflake] Get members after this user ID
|
|
@@ -462,7 +749,7 @@ module DiscordRDA
|
|
|
462
749
|
data.map { |m| Member.new(m.merge('guild_id' => guild_id.to_s)) }
|
|
463
750
|
end
|
|
464
751
|
|
|
465
|
-
# Search guild members by query
|
|
752
|
+
# Search guild members by query
|
|
466
753
|
# @param guild_id [String, Snowflake] Guild ID
|
|
467
754
|
# @param query [String] Search query (username/nickname prefix)
|
|
468
755
|
# @param limit [Integer] Max results (1-100, default 25)
|
|
@@ -473,7 +760,7 @@ module DiscordRDA
|
|
|
473
760
|
data.map { |m| Member.new(m.merge('guild_id' => guild_id.to_s)) }
|
|
474
761
|
end
|
|
475
762
|
|
|
476
|
-
# Modify a guild member
|
|
763
|
+
# Modify a guild member
|
|
477
764
|
# @param guild_id [String, Snowflake] Guild ID
|
|
478
765
|
# @param user_id [String, Snowflake] User ID
|
|
479
766
|
# @param options [Hash] Options to modify (nick, roles, mute, deaf, channel_id)
|
|
@@ -484,6 +771,42 @@ module DiscordRDA
|
|
|
484
771
|
Member.new(data.merge('guild_id' => guild_id.to_s))
|
|
485
772
|
end
|
|
486
773
|
|
|
774
|
+
# Add a member to a guild through OAuth2
|
|
775
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
776
|
+
# @param user_id [String, Snowflake] User ID
|
|
777
|
+
# @param access_token [String] User OAuth2 access token
|
|
778
|
+
# @param options [Hash] Optional member settings
|
|
779
|
+
# @return [Hash] Discord add-member response
|
|
780
|
+
def add_guild_member(guild_id, user_id, access_token:, **options)
|
|
781
|
+
payload = {
|
|
782
|
+
access_token: access_token,
|
|
783
|
+
nick: options[:nick],
|
|
784
|
+
roles: options[:roles],
|
|
785
|
+
mute: options[:mute],
|
|
786
|
+
deaf: options[:deaf]
|
|
787
|
+
}.compact
|
|
788
|
+
@rest.put("/guilds/#{guild_id}/members/#{user_id}", body: payload)
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
# Modify the current bot member in a guild
|
|
792
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
793
|
+
# @param nick [String, nil] New nickname
|
|
794
|
+
# @param reason [String, nil] Audit log reason
|
|
795
|
+
# @return [Member] Updated member
|
|
796
|
+
def modify_current_member(guild_id, nick: nil, reason: nil)
|
|
797
|
+
data = @rest.patch("/guilds/#{guild_id}/members/@me", body: { nick: nick }.compact, headers: audit_log_headers(reason))
|
|
798
|
+
Member.new(data.merge('guild_id' => guild_id.to_s))
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
# Modify the current bot nickname in a guild
|
|
802
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
803
|
+
# @param nick [String, nil] New nickname
|
|
804
|
+
# @param reason [String, nil] Audit log reason
|
|
805
|
+
# @return [Hash] Discord nickname response
|
|
806
|
+
def modify_current_user_nick(guild_id, nick: nil, reason: nil)
|
|
807
|
+
@rest.patch("/guilds/#{guild_id}/members/@me/nick", body: { nick: nick }.compact, headers: audit_log_headers(reason))
|
|
808
|
+
end
|
|
809
|
+
|
|
487
810
|
# Add role to guild member
|
|
488
811
|
# @param guild_id [String, Snowflake] Guild ID
|
|
489
812
|
# @param user_id [String, Snowflake] User ID
|
|
@@ -516,7 +839,7 @@ module DiscordRDA
|
|
|
516
839
|
@rest.delete("/guilds/#{guild_id}/members/#{user_id}", headers: headers)
|
|
517
840
|
end
|
|
518
841
|
|
|
519
|
-
# === Guild Roles
|
|
842
|
+
# === Guild Roles ===
|
|
520
843
|
|
|
521
844
|
# Get guild roles
|
|
522
845
|
# @param guild_id [String, Snowflake] Guild ID
|
|
@@ -526,14 +849,14 @@ module DiscordRDA
|
|
|
526
849
|
data.map { |r| Role.new(r.merge('guild_id' => guild_id.to_s)) }
|
|
527
850
|
end
|
|
528
851
|
|
|
529
|
-
# Create guild role
|
|
852
|
+
# Create guild role
|
|
530
853
|
# @param guild_id [String, Snowflake] Guild ID
|
|
531
854
|
# @param name [String] Role name
|
|
532
855
|
# @param options [Hash] Optional settings (permissions, color, hoist, mentionable)
|
|
533
856
|
# @return [Role] Created role
|
|
534
|
-
def create_guild_role(guild_id, name:, **options)
|
|
857
|
+
def create_guild_role(guild_id, name:, reason: nil, **options)
|
|
535
858
|
payload = { name: name }.merge(options.slice(:permissions, :color, :hoist, :mentionable, :icon, :unicode_emoji))
|
|
536
|
-
data = @rest.post("/guilds/#{guild_id}/roles", body: payload)
|
|
859
|
+
data = @rest.post("/guilds/#{guild_id}/roles", body: payload, headers: audit_log_headers(reason))
|
|
537
860
|
Role.new(data.merge('guild_id' => guild_id.to_s))
|
|
538
861
|
end
|
|
539
862
|
|
|
@@ -542,12 +865,25 @@ module DiscordRDA
|
|
|
542
865
|
# @param role_id [String, Snowflake] Role ID
|
|
543
866
|
# @param options [Hash] Settings to modify
|
|
544
867
|
# @return [Role] Updated role
|
|
545
|
-
def modify_guild_role(guild_id, role_id, **options)
|
|
868
|
+
def modify_guild_role(guild_id, role_id, reason: nil, **options)
|
|
546
869
|
payload = options.slice(:name, :permissions, :color, :hoist, :mentionable, :icon, :unicode_emoji)
|
|
547
|
-
data = @rest.patch("/guilds/#{guild_id}/roles/#{role_id}", body: payload)
|
|
870
|
+
data = @rest.patch("/guilds/#{guild_id}/roles/#{role_id}", body: payload, headers: audit_log_headers(reason))
|
|
548
871
|
Role.new(data.merge('guild_id' => guild_id.to_s))
|
|
549
872
|
end
|
|
550
873
|
|
|
874
|
+
# Modify guild role positions
|
|
875
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
876
|
+
# @param positions [Array<Hash>] Array of { id:, position: }
|
|
877
|
+
# @param reason [String, nil] Audit log reason
|
|
878
|
+
# @return [Array<Role>] Updated role ordering
|
|
879
|
+
def modify_guild_role_positions(guild_id, positions, reason: nil)
|
|
880
|
+
payload = positions.map do |position|
|
|
881
|
+
{ id: (position[:id] || position['id']).to_s, position: position[:position] || position['position'] }
|
|
882
|
+
end
|
|
883
|
+
data = @rest.patch("/guilds/#{guild_id}/roles", body: payload, headers: audit_log_headers(reason))
|
|
884
|
+
data.map { |role| Role.new(role.merge('guild_id' => guild_id.to_s)) }
|
|
885
|
+
end
|
|
886
|
+
|
|
551
887
|
# Delete guild role
|
|
552
888
|
# @param guild_id [String, Snowflake] Guild ID
|
|
553
889
|
# @param role_id [String, Snowflake] Role ID
|
|
@@ -558,14 +894,19 @@ module DiscordRDA
|
|
|
558
894
|
@rest.delete("/guilds/#{guild_id}/roles/#{role_id}", headers: headers)
|
|
559
895
|
end
|
|
560
896
|
|
|
561
|
-
# === Guild Bans
|
|
897
|
+
# === Guild Bans ===
|
|
562
898
|
|
|
563
|
-
# Get guild bans
|
|
899
|
+
# Get guild bans
|
|
564
900
|
# @param guild_id [String, Snowflake] Guild ID
|
|
565
901
|
# @param limit [Integer] Max bans (1-1000, default 100)
|
|
902
|
+
# @param before [String, Snowflake, nil] Cursor for pagination
|
|
903
|
+
# @param after [String, Snowflake, nil] Cursor for pagination
|
|
566
904
|
# @return [Array<Hash>] Bans (user + reason data)
|
|
567
|
-
def guild_bans(guild_id, limit: 100)
|
|
568
|
-
|
|
905
|
+
def guild_bans(guild_id, limit: 100, before: nil, after: nil)
|
|
906
|
+
params = { limit: limit }
|
|
907
|
+
params[:before] = before.to_s if before
|
|
908
|
+
params[:after] = after.to_s if after
|
|
909
|
+
data = @rest.get("/guilds/#{guild_id}/bans", params: params)
|
|
569
910
|
data.map { |b| { user: User.new(b['user']), reason: b['reason'] } }
|
|
570
911
|
end
|
|
571
912
|
|
|
@@ -603,42 +944,48 @@ module DiscordRDA
|
|
|
603
944
|
@rest.delete("/guilds/#{guild_id}/bans/#{user_id}", headers: headers)
|
|
604
945
|
end
|
|
605
946
|
|
|
606
|
-
# === Webhooks
|
|
947
|
+
# === Webhooks ===
|
|
607
948
|
|
|
608
949
|
# Create a webhook
|
|
609
950
|
# @param channel_id [String, Snowflake] Channel ID
|
|
610
951
|
# @param name [String] Webhook name
|
|
611
952
|
# @param avatar [String] Base64-encoded avatar image (optional)
|
|
612
|
-
# @return [
|
|
613
|
-
def create_webhook(channel_id, name:, avatar: nil)
|
|
953
|
+
# @return [Webhook] Webhook data
|
|
954
|
+
def create_webhook(channel_id, name:, avatar: nil, reason: nil)
|
|
614
955
|
payload = { name: name }
|
|
615
956
|
payload[:avatar] = avatar if avatar
|
|
616
|
-
@rest.post("/channels/#{channel_id}/webhooks", body: payload)
|
|
957
|
+
Webhook.new(@rest.post("/channels/#{channel_id}/webhooks", body: payload, headers: audit_log_headers(reason)))
|
|
617
958
|
end
|
|
618
959
|
|
|
619
960
|
# Get channel webhooks
|
|
620
961
|
# @param channel_id [String, Snowflake] Channel ID
|
|
621
|
-
# @return [Array<
|
|
962
|
+
# @return [Array<Webhook>] Webhooks
|
|
622
963
|
def channel_webhooks(channel_id)
|
|
623
|
-
@rest.get("/channels/#{channel_id}/webhooks")
|
|
964
|
+
@rest.get("/channels/#{channel_id}/webhooks").map { |hook| Webhook.new(hook) }
|
|
624
965
|
end
|
|
625
966
|
|
|
626
967
|
# Get guild webhooks
|
|
627
968
|
# @param guild_id [String, Snowflake] Guild ID
|
|
628
|
-
# @return [Array<
|
|
969
|
+
# @return [Array<Webhook>] Webhooks
|
|
629
970
|
def guild_webhooks(guild_id)
|
|
630
|
-
@rest.get("/guilds/#{guild_id}/webhooks")
|
|
971
|
+
@rest.get("/guilds/#{guild_id}/webhooks").map { |hook| Webhook.new(hook) }
|
|
631
972
|
end
|
|
632
973
|
|
|
633
|
-
# Execute webhook
|
|
974
|
+
# Execute webhook
|
|
634
975
|
# @param webhook_id [String, Snowflake] Webhook ID
|
|
635
976
|
# @param token [String] Webhook token
|
|
636
977
|
# @param content [String] Message content
|
|
637
978
|
# @param options [Hash] Options (username, avatar_url, embeds, etc.)
|
|
638
|
-
# @
|
|
979
|
+
# @option options [Boolean] :wait Return the created message
|
|
980
|
+
# @option options [String, Snowflake] :thread_id Execute in a thread
|
|
981
|
+
# @return [Message, nil]
|
|
639
982
|
def execute_webhook(webhook_id, token, content = nil, **options)
|
|
640
|
-
|
|
641
|
-
|
|
983
|
+
params = {}
|
|
984
|
+
params[:wait] = options[:wait] unless options[:wait].nil?
|
|
985
|
+
params[:thread_id] = options[:thread_id].to_s if options[:thread_id]
|
|
986
|
+
payload = { content: content }.merge(options.slice(:username, :avatar_url, :embeds, :components, :allowed_mentions, :tts))
|
|
987
|
+
response = @rest.post("/webhooks/#{webhook_id}/#{token}", body: payload, params: params)
|
|
988
|
+
response.is_a?(Hash) ? Message.new(response) : nil
|
|
642
989
|
end
|
|
643
990
|
|
|
644
991
|
# Delete a webhook
|
|
@@ -650,7 +997,109 @@ module DiscordRDA
|
|
|
650
997
|
@rest.delete(path)
|
|
651
998
|
end
|
|
652
999
|
|
|
653
|
-
#
|
|
1000
|
+
# Get a webhook by ID
|
|
1001
|
+
# @param webhook_id [String, Snowflake] Webhook ID
|
|
1002
|
+
# @return [Webhook, nil] Webhook payload
|
|
1003
|
+
def webhook(webhook_id)
|
|
1004
|
+
Webhook.new(@rest.get("/webhooks/#{webhook_id}"))
|
|
1005
|
+
rescue RestClient::NotFoundError
|
|
1006
|
+
nil
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
# Get a webhook by ID and token
|
|
1010
|
+
# @param webhook_id [String, Snowflake] Webhook ID
|
|
1011
|
+
# @param token [String] Webhook token
|
|
1012
|
+
# @return [Webhook, nil] Webhook payload
|
|
1013
|
+
def webhook_with_token(webhook_id, token)
|
|
1014
|
+
Webhook.new(@rest.get("/webhooks/#{webhook_id}/#{token}"))
|
|
1015
|
+
rescue RestClient::NotFoundError
|
|
1016
|
+
nil
|
|
1017
|
+
end
|
|
1018
|
+
|
|
1019
|
+
# Modify a webhook
|
|
1020
|
+
# @param webhook_id [String, Snowflake] Webhook ID
|
|
1021
|
+
# @param name [String, nil] New webhook name
|
|
1022
|
+
# @param avatar [String, nil] Base64 avatar data
|
|
1023
|
+
# @param channel_id [String, Snowflake, nil] New channel ID
|
|
1024
|
+
# @return [Webhook] Updated webhook payload
|
|
1025
|
+
def modify_webhook(webhook_id, name: nil, avatar: nil, channel_id: nil, reason: nil)
|
|
1026
|
+
payload = { name: name, avatar: avatar, channel_id: channel_id&.to_s }.compact
|
|
1027
|
+
Webhook.new(@rest.patch("/webhooks/#{webhook_id}", body: payload, headers: audit_log_headers(reason)))
|
|
1028
|
+
end
|
|
1029
|
+
|
|
1030
|
+
# Modify a webhook using its token
|
|
1031
|
+
# @param webhook_id [String, Snowflake] Webhook ID
|
|
1032
|
+
# @param token [String] Webhook token
|
|
1033
|
+
# @param name [String, nil] New webhook name
|
|
1034
|
+
# @param avatar [String, nil] Base64 avatar data
|
|
1035
|
+
# @return [Webhook] Updated webhook payload
|
|
1036
|
+
def modify_webhook_with_token(webhook_id, token, name: nil, avatar: nil)
|
|
1037
|
+
payload = { name: name, avatar: avatar }.compact
|
|
1038
|
+
Webhook.new(@rest.patch("/webhooks/#{webhook_id}/#{token}", body: payload))
|
|
1039
|
+
end
|
|
1040
|
+
|
|
1041
|
+
# Execute a Slack-compatible webhook
|
|
1042
|
+
# @param webhook_id [String, Snowflake] Webhook ID
|
|
1043
|
+
# @param token [String] Webhook token
|
|
1044
|
+
# @param payload [Hash] Slack webhook payload
|
|
1045
|
+
# @return [Object] API response
|
|
1046
|
+
def execute_slack_webhook(webhook_id, token, payload)
|
|
1047
|
+
@rest.post("/webhooks/#{webhook_id}/#{token}/slack", body: payload)
|
|
1048
|
+
end
|
|
1049
|
+
|
|
1050
|
+
# Execute a GitHub-compatible webhook
|
|
1051
|
+
# @param webhook_id [String, Snowflake] Webhook ID
|
|
1052
|
+
# @param token [String] Webhook token
|
|
1053
|
+
# @param payload [Hash] GitHub webhook payload
|
|
1054
|
+
# @return [Object] API response
|
|
1055
|
+
def execute_github_webhook(webhook_id, token, payload)
|
|
1056
|
+
@rest.post("/webhooks/#{webhook_id}/#{token}/github", body: payload)
|
|
1057
|
+
end
|
|
1058
|
+
|
|
1059
|
+
# Get a webhook message
|
|
1060
|
+
# @param webhook_id [String, Snowflake] Webhook ID
|
|
1061
|
+
# @param token [String] Webhook token
|
|
1062
|
+
# @param message_id [String, Snowflake] Message ID
|
|
1063
|
+
# @param thread_id [String, Snowflake, nil] Thread channel ID
|
|
1064
|
+
# @return [Message, nil] Webhook message
|
|
1065
|
+
def webhook_message(webhook_id, token, message_id, thread_id: nil)
|
|
1066
|
+
params = {}
|
|
1067
|
+
params[:thread_id] = thread_id.to_s if thread_id
|
|
1068
|
+
data = @rest.get("/webhooks/#{webhook_id}/#{token}/messages/#{message_id}", params: params)
|
|
1069
|
+
Message.new(data)
|
|
1070
|
+
rescue RestClient::NotFoundError
|
|
1071
|
+
nil
|
|
1072
|
+
end
|
|
1073
|
+
|
|
1074
|
+
# Edit a webhook message
|
|
1075
|
+
# @param webhook_id [String, Snowflake] Webhook ID
|
|
1076
|
+
# @param token [String] Webhook token
|
|
1077
|
+
# @param message_id [String, Snowflake] Message ID
|
|
1078
|
+
# @param thread_id [String, Snowflake, nil] Thread channel ID
|
|
1079
|
+
# @param content [String, nil] New content
|
|
1080
|
+
# @param options [Hash] Additional edit payload
|
|
1081
|
+
# @return [Message] Updated message
|
|
1082
|
+
def edit_webhook_message(webhook_id, token, message_id, thread_id: nil, content: nil, **options)
|
|
1083
|
+
params = {}
|
|
1084
|
+
params[:thread_id] = thread_id.to_s if thread_id
|
|
1085
|
+
payload = { content: content }.merge(options).compact
|
|
1086
|
+
data = @rest.patch("/webhooks/#{webhook_id}/#{token}/messages/#{message_id}", body: payload, params: params)
|
|
1087
|
+
Message.new(data)
|
|
1088
|
+
end
|
|
1089
|
+
|
|
1090
|
+
# Delete a webhook message
|
|
1091
|
+
# @param webhook_id [String, Snowflake] Webhook ID
|
|
1092
|
+
# @param token [String] Webhook token
|
|
1093
|
+
# @param message_id [String, Snowflake] Message ID
|
|
1094
|
+
# @param thread_id [String, Snowflake, nil] Thread channel ID
|
|
1095
|
+
# @return [void]
|
|
1096
|
+
def delete_webhook_message(webhook_id, token, message_id, thread_id: nil)
|
|
1097
|
+
params = {}
|
|
1098
|
+
params[:thread_id] = thread_id.to_s if thread_id
|
|
1099
|
+
@rest.delete("/webhooks/#{webhook_id}/#{token}/messages/#{message_id}", params: params)
|
|
1100
|
+
end
|
|
1101
|
+
|
|
1102
|
+
# === Channel Management ===
|
|
654
1103
|
|
|
655
1104
|
# Get guild channels
|
|
656
1105
|
# @param guild_id [String, Snowflake] Guild ID
|
|
@@ -660,7 +1109,547 @@ module DiscordRDA
|
|
|
660
1109
|
data.map { |c| Channel.new(c) }
|
|
661
1110
|
end
|
|
662
1111
|
|
|
663
|
-
#
|
|
1112
|
+
# Get a guild preview
|
|
1113
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1114
|
+
# @return [Hash, nil] Guild preview payload
|
|
1115
|
+
def guild_preview(guild_id)
|
|
1116
|
+
@rest.get("/guilds/#{guild_id}/preview")
|
|
1117
|
+
rescue RestClient::NotFoundError
|
|
1118
|
+
nil
|
|
1119
|
+
end
|
|
1120
|
+
|
|
1121
|
+
# Get the expected prune count for a guild
|
|
1122
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1123
|
+
# @param days [Integer] Inactive days threshold
|
|
1124
|
+
# @param include_roles [Array<String, Snowflake>] Optional role IDs
|
|
1125
|
+
# @return [Integer, nil] Number of prunable members
|
|
1126
|
+
def guild_prune_count(guild_id, days:, include_roles: nil)
|
|
1127
|
+
params = { days: days }
|
|
1128
|
+
params[:include_roles] = Array(include_roles).map(&:to_s).join(',') if include_roles
|
|
1129
|
+
@rest.get("/guilds/#{guild_id}/prune", params: params)['pruned']
|
|
1130
|
+
end
|
|
1131
|
+
|
|
1132
|
+
# Begin a guild prune
|
|
1133
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1134
|
+
# @param days [Integer] Inactive days threshold
|
|
1135
|
+
# @param compute_prune_count [Boolean] Whether to include the prune count
|
|
1136
|
+
# @param include_roles [Array<String, Snowflake>] Optional role IDs
|
|
1137
|
+
# @param reason [String, nil] Audit log reason
|
|
1138
|
+
# @return [Integer, nil] Number of pruned members when requested
|
|
1139
|
+
def begin_guild_prune(guild_id, days:, compute_prune_count: true, include_roles: nil, reason: nil)
|
|
1140
|
+
payload = { days: days, compute_prune_count: compute_prune_count }
|
|
1141
|
+
payload[:include_roles] = Array(include_roles).map(&:to_s) if include_roles
|
|
1142
|
+
response = @rest.post("/guilds/#{guild_id}/prune", body: payload, headers: audit_log_headers(reason))
|
|
1143
|
+
response && response['pruned']
|
|
1144
|
+
end
|
|
1145
|
+
|
|
1146
|
+
# Get voice regions for a guild
|
|
1147
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1148
|
+
# @return [Array<Hash>] Voice regions
|
|
1149
|
+
def guild_voice_regions(guild_id)
|
|
1150
|
+
@rest.get("/guilds/#{guild_id}/regions")
|
|
1151
|
+
end
|
|
1152
|
+
|
|
1153
|
+
# Get active invites for a guild
|
|
1154
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1155
|
+
# @return [Array<Hash>] Invite payloads
|
|
1156
|
+
def guild_invites(guild_id)
|
|
1157
|
+
@rest.get("/guilds/#{guild_id}/invites")
|
|
1158
|
+
end
|
|
1159
|
+
|
|
1160
|
+
# Get integrations for a guild
|
|
1161
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1162
|
+
# @return [Array<Integration>] Integration payloads
|
|
1163
|
+
def guild_integrations(guild_id)
|
|
1164
|
+
@rest.get("/guilds/#{guild_id}/integrations").map { |integration| Integration.new(integration.merge('guild_id' => guild_id.to_s)) }
|
|
1165
|
+
end
|
|
1166
|
+
|
|
1167
|
+
# Delete a guild integration
|
|
1168
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1169
|
+
# @param integration_id [String, Snowflake] Integration ID
|
|
1170
|
+
# @param reason [String, nil] Audit log reason
|
|
1171
|
+
# @return [void]
|
|
1172
|
+
def delete_guild_integration(guild_id, integration_id, reason: nil)
|
|
1173
|
+
@rest.delete("/guilds/#{guild_id}/integrations/#{integration_id}", headers: audit_log_headers(reason))
|
|
1174
|
+
end
|
|
1175
|
+
|
|
1176
|
+
# Get guild widget settings
|
|
1177
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1178
|
+
# @return [Hash, nil] Widget settings
|
|
1179
|
+
def guild_widget_settings(guild_id)
|
|
1180
|
+
@rest.get("/guilds/#{guild_id}/widget")
|
|
1181
|
+
rescue RestClient::NotFoundError
|
|
1182
|
+
nil
|
|
1183
|
+
end
|
|
1184
|
+
|
|
1185
|
+
# Modify guild widget settings
|
|
1186
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1187
|
+
# @param enabled [Boolean, nil] Whether widget is enabled
|
|
1188
|
+
# @param channel_id [String, Snowflake, nil] Widget channel ID
|
|
1189
|
+
# @param reason [String, nil] Audit log reason
|
|
1190
|
+
# @return [Hash] Updated widget settings
|
|
1191
|
+
def modify_guild_widget(guild_id, enabled: nil, channel_id: nil, reason: nil)
|
|
1192
|
+
payload = { enabled: enabled, channel_id: channel_id&.to_s }.compact
|
|
1193
|
+
@rest.patch("/guilds/#{guild_id}/widget", body: payload, headers: audit_log_headers(reason))
|
|
1194
|
+
end
|
|
1195
|
+
|
|
1196
|
+
# Get guild widget data
|
|
1197
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1198
|
+
# @return [Hash, nil] Widget payload
|
|
1199
|
+
def guild_widget(guild_id)
|
|
1200
|
+
@rest.get("/guilds/#{guild_id}/widget.json")
|
|
1201
|
+
rescue RestClient::NotFoundError
|
|
1202
|
+
nil
|
|
1203
|
+
end
|
|
1204
|
+
|
|
1205
|
+
# Get a guild vanity URL
|
|
1206
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1207
|
+
# @return [Hash, nil] Vanity URL payload
|
|
1208
|
+
def guild_vanity_url(guild_id)
|
|
1209
|
+
@rest.get("/guilds/#{guild_id}/vanity-url")
|
|
1210
|
+
rescue RestClient::NotFoundError
|
|
1211
|
+
nil
|
|
1212
|
+
end
|
|
1213
|
+
|
|
1214
|
+
# Build a guild widget image URL
|
|
1215
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1216
|
+
# @param style [String, nil] Widget image style
|
|
1217
|
+
# @return [String] Widget image URL
|
|
1218
|
+
def guild_widget_image(guild_id, style: nil)
|
|
1219
|
+
url = "https://discord.com/api/guilds/#{guild_id}/widget.png"
|
|
1220
|
+
style ? "#{url}?style=#{CGI.escape(style)}" : url
|
|
1221
|
+
end
|
|
1222
|
+
|
|
1223
|
+
# Get a guild welcome screen
|
|
1224
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1225
|
+
# @return [Hash, nil] Welcome screen payload
|
|
1226
|
+
def guild_welcome_screen(guild_id)
|
|
1227
|
+
@rest.get("/guilds/#{guild_id}/welcome-screen")
|
|
1228
|
+
rescue RestClient::NotFoundError
|
|
1229
|
+
nil
|
|
1230
|
+
end
|
|
1231
|
+
|
|
1232
|
+
# Modify a guild welcome screen
|
|
1233
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1234
|
+
# @param enabled [Boolean, nil] Whether enabled
|
|
1235
|
+
# @param welcome_channels [Array<Hash>, nil] Welcome channel configuration
|
|
1236
|
+
# @param description [String, nil] Welcome description
|
|
1237
|
+
# @param reason [String, nil] Audit log reason
|
|
1238
|
+
# @return [Hash] Updated welcome screen payload
|
|
1239
|
+
def modify_guild_welcome_screen(guild_id, enabled: nil, welcome_channels: nil, description: nil, reason: nil)
|
|
1240
|
+
payload = {
|
|
1241
|
+
enabled: enabled,
|
|
1242
|
+
welcome_channels: welcome_channels,
|
|
1243
|
+
description: description
|
|
1244
|
+
}.compact
|
|
1245
|
+
@rest.patch("/guilds/#{guild_id}/welcome-screen", body: payload, headers: audit_log_headers(reason))
|
|
1246
|
+
end
|
|
1247
|
+
|
|
1248
|
+
# Get guild onboarding
|
|
1249
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1250
|
+
# @return [Hash, nil] Onboarding payload
|
|
1251
|
+
def guild_onboarding(guild_id)
|
|
1252
|
+
@rest.get("/guilds/#{guild_id}/onboarding")
|
|
1253
|
+
rescue RestClient::NotFoundError
|
|
1254
|
+
nil
|
|
1255
|
+
end
|
|
1256
|
+
|
|
1257
|
+
# Modify guild onboarding
|
|
1258
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1259
|
+
# @param options [Hash] Raw onboarding payload
|
|
1260
|
+
# @return [Hash] Updated onboarding payload
|
|
1261
|
+
def modify_guild_onboarding(guild_id, **options)
|
|
1262
|
+
@rest.put("/guilds/#{guild_id}/onboarding", body: options)
|
|
1263
|
+
end
|
|
1264
|
+
|
|
1265
|
+
# Get audit log entries for a guild
|
|
1266
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1267
|
+
# @param user_id [String, Snowflake, nil] Filter by acting user
|
|
1268
|
+
# @param action_type [Integer, nil] Filter by action type
|
|
1269
|
+
# @param before [String, Snowflake, nil] Pagination cursor
|
|
1270
|
+
# @param after [String, Snowflake, nil] Pagination cursor
|
|
1271
|
+
# @param limit [Integer, nil] Max entries
|
|
1272
|
+
# @return [AuditLog] Audit log payload
|
|
1273
|
+
def guild_audit_log(guild_id, user_id: nil, action_type: nil, before: nil, after: nil, limit: nil)
|
|
1274
|
+
params = {
|
|
1275
|
+
user_id: user_id&.to_s,
|
|
1276
|
+
action_type: action_type,
|
|
1277
|
+
before: before&.to_s,
|
|
1278
|
+
after: after&.to_s,
|
|
1279
|
+
limit: limit
|
|
1280
|
+
}.compact
|
|
1281
|
+
AuditLog.new(@rest.get("/guilds/#{guild_id}/audit-logs", params: params))
|
|
1282
|
+
end
|
|
1283
|
+
|
|
1284
|
+
# List scheduled events for a guild
|
|
1285
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1286
|
+
# @param with_user_count [Boolean] Include subscriber counts
|
|
1287
|
+
# @return [Array<GuildScheduledEvent>] Scheduled events
|
|
1288
|
+
def guild_scheduled_events(guild_id, with_user_count: false)
|
|
1289
|
+
data = @rest.get("/guilds/#{guild_id}/scheduled-events", params: { with_user_count: with_user_count })
|
|
1290
|
+
data.map { |event| GuildScheduledEvent.new(event) }
|
|
1291
|
+
end
|
|
1292
|
+
|
|
1293
|
+
# Get a specific scheduled event
|
|
1294
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1295
|
+
# @param event_id [String, Snowflake] Event ID
|
|
1296
|
+
# @param with_user_count [Boolean] Include subscriber count
|
|
1297
|
+
# @return [GuildScheduledEvent, nil] Scheduled event
|
|
1298
|
+
def guild_scheduled_event(guild_id, event_id, with_user_count: false)
|
|
1299
|
+
data = @rest.get("/guilds/#{guild_id}/scheduled-events/#{event_id}", params: { with_user_count: with_user_count })
|
|
1300
|
+
GuildScheduledEvent.new(data)
|
|
1301
|
+
rescue RestClient::NotFoundError
|
|
1302
|
+
nil
|
|
1303
|
+
end
|
|
1304
|
+
|
|
1305
|
+
# Create a scheduled event
|
|
1306
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1307
|
+
# @param reason [String, nil] Audit log reason
|
|
1308
|
+
# @param options [Hash] Scheduled event payload
|
|
1309
|
+
# @return [GuildScheduledEvent] Created event
|
|
1310
|
+
def create_guild_scheduled_event(guild_id, reason: nil, **options)
|
|
1311
|
+
data = @rest.post("/guilds/#{guild_id}/scheduled-events", body: options.compact, headers: audit_log_headers(reason))
|
|
1312
|
+
GuildScheduledEvent.new(data)
|
|
1313
|
+
end
|
|
1314
|
+
|
|
1315
|
+
# Modify a scheduled event
|
|
1316
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1317
|
+
# @param event_id [String, Snowflake] Event ID
|
|
1318
|
+
# @param reason [String, nil] Audit log reason
|
|
1319
|
+
# @param options [Hash] Scheduled event payload
|
|
1320
|
+
# @return [GuildScheduledEvent] Updated event
|
|
1321
|
+
def modify_guild_scheduled_event(guild_id, event_id, reason: nil, **options)
|
|
1322
|
+
data = @rest.patch("/guilds/#{guild_id}/scheduled-events/#{event_id}", body: options.compact, headers: audit_log_headers(reason))
|
|
1323
|
+
GuildScheduledEvent.new(data)
|
|
1324
|
+
end
|
|
1325
|
+
|
|
1326
|
+
# Delete a scheduled event
|
|
1327
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1328
|
+
# @param event_id [String, Snowflake] Event ID
|
|
1329
|
+
# @return [void]
|
|
1330
|
+
def delete_guild_scheduled_event(guild_id, event_id)
|
|
1331
|
+
@rest.delete("/guilds/#{guild_id}/scheduled-events/#{event_id}")
|
|
1332
|
+
end
|
|
1333
|
+
|
|
1334
|
+
# List users subscribed to a scheduled event
|
|
1335
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1336
|
+
# @param event_id [String, Snowflake] Event ID
|
|
1337
|
+
# @param limit [Integer, nil] Max users
|
|
1338
|
+
# @param with_member [Boolean] Include member objects
|
|
1339
|
+
# @param before [String, Snowflake, nil] Pagination cursor
|
|
1340
|
+
# @param after [String, Snowflake, nil] Pagination cursor
|
|
1341
|
+
# @return [Array<Hash>] Subscriber payloads
|
|
1342
|
+
def guild_scheduled_event_users(guild_id, event_id, limit: nil, with_member: false, before: nil, after: nil)
|
|
1343
|
+
params = {
|
|
1344
|
+
limit: limit,
|
|
1345
|
+
with_member: with_member,
|
|
1346
|
+
before: before&.to_s,
|
|
1347
|
+
after: after&.to_s
|
|
1348
|
+
}.compact
|
|
1349
|
+
@rest.get("/guilds/#{guild_id}/scheduled-events/#{event_id}/users", params: params)
|
|
1350
|
+
end
|
|
1351
|
+
|
|
1352
|
+
# Create a stage instance
|
|
1353
|
+
# @param channel_id [String, Snowflake] Stage channel ID
|
|
1354
|
+
# @param topic [String] Stage topic
|
|
1355
|
+
# @param privacy_level [Integer, nil] Privacy level
|
|
1356
|
+
# @param send_start_notification [Boolean, nil] Send notification
|
|
1357
|
+
# @param guild_scheduled_event_id [String, Snowflake, nil] Associated scheduled event
|
|
1358
|
+
# @return [Hash] Stage instance payload
|
|
1359
|
+
def create_stage_instance(channel_id:, topic:, privacy_level: nil, send_start_notification: nil, guild_scheduled_event_id: nil)
|
|
1360
|
+
payload = {
|
|
1361
|
+
channel_id: channel_id.to_s,
|
|
1362
|
+
topic: topic,
|
|
1363
|
+
privacy_level: privacy_level,
|
|
1364
|
+
send_start_notification: send_start_notification,
|
|
1365
|
+
guild_scheduled_event_id: guild_scheduled_event_id&.to_s
|
|
1366
|
+
}.compact
|
|
1367
|
+
@rest.post('/stage-instances', body: payload)
|
|
1368
|
+
end
|
|
1369
|
+
|
|
1370
|
+
# Get a stage instance
|
|
1371
|
+
# @param channel_id [String, Snowflake] Stage channel ID
|
|
1372
|
+
# @return [Hash, nil] Stage instance payload
|
|
1373
|
+
def stage_instance(channel_id)
|
|
1374
|
+
@rest.get("/stage-instances/#{channel_id}")
|
|
1375
|
+
rescue RestClient::NotFoundError
|
|
1376
|
+
nil
|
|
1377
|
+
end
|
|
1378
|
+
|
|
1379
|
+
# Modify a stage instance
|
|
1380
|
+
# @param channel_id [String, Snowflake] Stage channel ID
|
|
1381
|
+
# @param topic [String, nil] Updated topic
|
|
1382
|
+
# @param privacy_level [Integer, nil] Updated privacy level
|
|
1383
|
+
# @return [Hash] Updated stage instance payload
|
|
1384
|
+
def modify_stage_instance(channel_id, topic: nil, privacy_level: nil)
|
|
1385
|
+
@rest.patch("/stage-instances/#{channel_id}", body: { topic: topic, privacy_level: privacy_level }.compact)
|
|
1386
|
+
end
|
|
1387
|
+
|
|
1388
|
+
# Delete a stage instance
|
|
1389
|
+
# @param channel_id [String, Snowflake] Stage channel ID
|
|
1390
|
+
# @param reason [String, nil] Audit log reason
|
|
1391
|
+
# @return [void]
|
|
1392
|
+
def delete_stage_instance(channel_id, reason: nil)
|
|
1393
|
+
@rest.delete("/stage-instances/#{channel_id}", headers: audit_log_headers(reason))
|
|
1394
|
+
end
|
|
1395
|
+
|
|
1396
|
+
# List stickers for a guild
|
|
1397
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1398
|
+
# @return [Array<Sticker>] Guild stickers
|
|
1399
|
+
def guild_stickers(guild_id)
|
|
1400
|
+
data = @rest.get("/guilds/#{guild_id}/stickers")
|
|
1401
|
+
data.map { |sticker| Sticker.new(sticker) }
|
|
1402
|
+
end
|
|
1403
|
+
|
|
1404
|
+
# Get a guild sticker
|
|
1405
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1406
|
+
# @param sticker_id [String, Snowflake] Sticker ID
|
|
1407
|
+
# @return [Sticker, nil] Sticker
|
|
1408
|
+
def guild_sticker(guild_id, sticker_id)
|
|
1409
|
+
data = @rest.get("/guilds/#{guild_id}/stickers/#{sticker_id}")
|
|
1410
|
+
Sticker.new(data)
|
|
1411
|
+
rescue RestClient::NotFoundError
|
|
1412
|
+
nil
|
|
1413
|
+
end
|
|
1414
|
+
|
|
1415
|
+
# Get a standard sticker
|
|
1416
|
+
# @param sticker_id [String, Snowflake] Sticker ID
|
|
1417
|
+
# @return [Sticker, nil] Sticker
|
|
1418
|
+
def sticker(sticker_id)
|
|
1419
|
+
data = @rest.get("/stickers/#{sticker_id}")
|
|
1420
|
+
Sticker.new(data)
|
|
1421
|
+
rescue RestClient::NotFoundError
|
|
1422
|
+
nil
|
|
1423
|
+
end
|
|
1424
|
+
|
|
1425
|
+
# List available premium sticker packs
|
|
1426
|
+
# @return [Hash] Sticker pack payload
|
|
1427
|
+
def sticker_packs
|
|
1428
|
+
@rest.get('/sticker-packs')
|
|
1429
|
+
end
|
|
1430
|
+
|
|
1431
|
+
# Get guild templates
|
|
1432
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1433
|
+
# @return [Array<Hash>] Template payloads
|
|
1434
|
+
def guild_templates(guild_id)
|
|
1435
|
+
@rest.get("/guilds/#{guild_id}/templates")
|
|
1436
|
+
end
|
|
1437
|
+
|
|
1438
|
+
# Create a guild template
|
|
1439
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1440
|
+
# @param name [String] Template name
|
|
1441
|
+
# @param description [String, nil] Template description
|
|
1442
|
+
# @return [Hash] Template payload
|
|
1443
|
+
def create_guild_template(guild_id, name:, description: nil)
|
|
1444
|
+
@rest.post("/guilds/#{guild_id}/templates", body: { name: name, description: description }.compact)
|
|
1445
|
+
end
|
|
1446
|
+
|
|
1447
|
+
# Sync a guild template
|
|
1448
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1449
|
+
# @param code [String] Template code
|
|
1450
|
+
# @return [Hash] Template payload
|
|
1451
|
+
def sync_guild_template(guild_id, code)
|
|
1452
|
+
@rest.put("/guilds/#{guild_id}/templates/#{code}")
|
|
1453
|
+
end
|
|
1454
|
+
|
|
1455
|
+
# Modify a guild template
|
|
1456
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1457
|
+
# @param code [String] Template code
|
|
1458
|
+
# @param name [String, nil] New template name
|
|
1459
|
+
# @param description [String, nil] New template description
|
|
1460
|
+
# @return [Hash] Template payload
|
|
1461
|
+
def modify_guild_template(guild_id, code, name: nil, description: nil)
|
|
1462
|
+
payload = { name: name, description: description }.compact
|
|
1463
|
+
@rest.patch("/guilds/#{guild_id}/templates/#{code}", body: payload)
|
|
1464
|
+
end
|
|
1465
|
+
|
|
1466
|
+
# Delete a guild template
|
|
1467
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1468
|
+
# @param code [String] Template code
|
|
1469
|
+
# @return [Hash] Deleted template payload
|
|
1470
|
+
def delete_guild_template(guild_id, code)
|
|
1471
|
+
@rest.delete("/guilds/#{guild_id}/templates/#{code}")
|
|
1472
|
+
end
|
|
1473
|
+
|
|
1474
|
+
# Fetch a guild template by code
|
|
1475
|
+
# @param code [String] Template code
|
|
1476
|
+
# @return [Hash, nil] Template payload
|
|
1477
|
+
def guild_template(code)
|
|
1478
|
+
@rest.get("/guilds/templates/#{code}")
|
|
1479
|
+
rescue RestClient::NotFoundError
|
|
1480
|
+
nil
|
|
1481
|
+
end
|
|
1482
|
+
|
|
1483
|
+
# Create a guild from a template
|
|
1484
|
+
# @param code [String] Template code
|
|
1485
|
+
# @param name [String] Guild name
|
|
1486
|
+
# @param icon [String, nil] Base64 icon data
|
|
1487
|
+
# @return [Guild] Created guild
|
|
1488
|
+
def create_guild_from_template(code, name:, icon: nil)
|
|
1489
|
+
data = @rest.post("/guilds/templates/#{code}", body: { name: name, icon: icon }.compact)
|
|
1490
|
+
Guild.new(data)
|
|
1491
|
+
end
|
|
1492
|
+
|
|
1493
|
+
# List auto moderation rules for a guild
|
|
1494
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1495
|
+
# @return [Array<AutoModerationRule>] Rules
|
|
1496
|
+
def auto_moderation_rules(guild_id)
|
|
1497
|
+
data = @rest.get("/guilds/#{guild_id}/auto-moderation/rules")
|
|
1498
|
+
data.map { |rule| AutoModerationRule.new(rule) }
|
|
1499
|
+
end
|
|
1500
|
+
|
|
1501
|
+
# Get a specific auto moderation rule
|
|
1502
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1503
|
+
# @param rule_id [String, Snowflake] Rule ID
|
|
1504
|
+
# @return [AutoModerationRule, nil] Rule
|
|
1505
|
+
def auto_moderation_rule(guild_id, rule_id)
|
|
1506
|
+
data = @rest.get("/guilds/#{guild_id}/auto-moderation/rules/#{rule_id}")
|
|
1507
|
+
AutoModerationRule.new(data)
|
|
1508
|
+
rescue RestClient::NotFoundError
|
|
1509
|
+
nil
|
|
1510
|
+
end
|
|
1511
|
+
|
|
1512
|
+
# Create an auto moderation rule
|
|
1513
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1514
|
+
# @param reason [String, nil] Audit log reason
|
|
1515
|
+
# @param options [Hash] Rule payload
|
|
1516
|
+
# @return [AutoModerationRule] Created rule
|
|
1517
|
+
def create_auto_moderation_rule(guild_id, reason: nil, **options)
|
|
1518
|
+
data = @rest.post("/guilds/#{guild_id}/auto-moderation/rules", body: options.compact, headers: audit_log_headers(reason))
|
|
1519
|
+
AutoModerationRule.new(data)
|
|
1520
|
+
end
|
|
1521
|
+
|
|
1522
|
+
# Modify an auto moderation rule
|
|
1523
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1524
|
+
# @param rule_id [String, Snowflake] Rule ID
|
|
1525
|
+
# @param reason [String, nil] Audit log reason
|
|
1526
|
+
# @param options [Hash] Rule payload
|
|
1527
|
+
# @return [AutoModerationRule] Updated rule
|
|
1528
|
+
def modify_auto_moderation_rule(guild_id, rule_id, reason: nil, **options)
|
|
1529
|
+
data = @rest.patch("/guilds/#{guild_id}/auto-moderation/rules/#{rule_id}", body: options.compact, headers: audit_log_headers(reason))
|
|
1530
|
+
AutoModerationRule.new(data)
|
|
1531
|
+
end
|
|
1532
|
+
|
|
1533
|
+
# Delete an auto moderation rule
|
|
1534
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1535
|
+
# @param rule_id [String, Snowflake] Rule ID
|
|
1536
|
+
# @param reason [String, nil] Audit log reason
|
|
1537
|
+
# @return [void]
|
|
1538
|
+
def delete_auto_moderation_rule(guild_id, rule_id, reason: nil)
|
|
1539
|
+
@rest.delete("/guilds/#{guild_id}/auto-moderation/rules/#{rule_id}", headers: audit_log_headers(reason))
|
|
1540
|
+
end
|
|
1541
|
+
|
|
1542
|
+
# Get SKUs for the current application
|
|
1543
|
+
# @param application_id [String, Snowflake, nil] Application ID
|
|
1544
|
+
# @return [Array<Hash>] SKU payloads
|
|
1545
|
+
def skus(application_id: nil)
|
|
1546
|
+
app_id = application_id || application_id_for_rest
|
|
1547
|
+
@rest.get("/applications/#{app_id}/skus")
|
|
1548
|
+
end
|
|
1549
|
+
|
|
1550
|
+
# Get entitlements for the current application
|
|
1551
|
+
# @param application_id [String, Snowflake, nil] Application ID
|
|
1552
|
+
# @param options [Hash] Query filters
|
|
1553
|
+
# @return [Array<Hash>] Entitlement payloads
|
|
1554
|
+
def entitlements(application_id: nil, **options)
|
|
1555
|
+
app_id = application_id || application_id_for_rest
|
|
1556
|
+
params = normalize_rest_params(options, :before, :after, :guild_id, :user_id, :limit, :exclude_ended)
|
|
1557
|
+
params[:sku_ids] = Array(options[:sku_ids]).map(&:to_s).join(',') if options[:sku_ids]
|
|
1558
|
+
@rest.get("/applications/#{app_id}/entitlements", params: params)
|
|
1559
|
+
end
|
|
1560
|
+
|
|
1561
|
+
# Create a test entitlement
|
|
1562
|
+
# @param sku_id [String, Snowflake] SKU ID
|
|
1563
|
+
# @param owner_id [String, Snowflake] User or guild owner ID
|
|
1564
|
+
# @param owner_type [Integer] 1 for guild, 2 for user
|
|
1565
|
+
# @param application_id [String, Snowflake, nil] Application ID
|
|
1566
|
+
# @return [Hash] Entitlement payload
|
|
1567
|
+
def create_test_entitlement(sku_id:, owner_id:, owner_type:, application_id: nil)
|
|
1568
|
+
app_id = application_id || application_id_for_rest
|
|
1569
|
+
payload = { sku_id: sku_id.to_s, owner_id: owner_id.to_s, owner_type: owner_type }
|
|
1570
|
+
@rest.post("/applications/#{app_id}/entitlements", body: payload)
|
|
1571
|
+
end
|
|
1572
|
+
|
|
1573
|
+
# Delete a test entitlement
|
|
1574
|
+
# @param entitlement_id [String, Snowflake] Entitlement ID
|
|
1575
|
+
# @param application_id [String, Snowflake, nil] Application ID
|
|
1576
|
+
# @return [void]
|
|
1577
|
+
def delete_test_entitlement(entitlement_id, application_id: nil)
|
|
1578
|
+
app_id = application_id || application_id_for_rest
|
|
1579
|
+
@rest.delete("/applications/#{app_id}/entitlements/#{entitlement_id}")
|
|
1580
|
+
end
|
|
1581
|
+
|
|
1582
|
+
# Consume an entitlement
|
|
1583
|
+
# @param entitlement_id [String, Snowflake] Entitlement ID
|
|
1584
|
+
# @param application_id [String, Snowflake, nil] Application ID
|
|
1585
|
+
# @return [void]
|
|
1586
|
+
def consume_entitlement(entitlement_id, application_id: nil)
|
|
1587
|
+
app_id = application_id || application_id_for_rest
|
|
1588
|
+
@rest.post("/applications/#{app_id}/entitlements/#{entitlement_id}/consume")
|
|
1589
|
+
end
|
|
1590
|
+
|
|
1591
|
+
# Get default soundboard sounds
|
|
1592
|
+
# @return [Hash] Soundboard payload
|
|
1593
|
+
def default_soundboard_sounds
|
|
1594
|
+
@rest.get('/soundboard-default-sounds')
|
|
1595
|
+
end
|
|
1596
|
+
|
|
1597
|
+
# Get guild soundboard sounds
|
|
1598
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1599
|
+
# @return [Hash] Soundboard payload
|
|
1600
|
+
def guild_soundboard_sounds(guild_id)
|
|
1601
|
+
@rest.get("/guilds/#{guild_id}/soundboard-sounds")
|
|
1602
|
+
end
|
|
1603
|
+
|
|
1604
|
+
# Get a single guild soundboard sound
|
|
1605
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1606
|
+
# @param sound_id [String, Snowflake] Sound ID
|
|
1607
|
+
# @return [Hash, nil] Soundboard sound payload
|
|
1608
|
+
def guild_soundboard_sound(guild_id, sound_id)
|
|
1609
|
+
@rest.get("/guilds/#{guild_id}/soundboard-sounds/#{sound_id}")
|
|
1610
|
+
rescue RestClient::NotFoundError
|
|
1611
|
+
nil
|
|
1612
|
+
end
|
|
1613
|
+
|
|
1614
|
+
# Create a guild soundboard sound
|
|
1615
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1616
|
+
# @param reason [String, nil] Audit log reason
|
|
1617
|
+
# @param options [Hash] Soundboard payload
|
|
1618
|
+
# @return [Hash] Soundboard sound payload
|
|
1619
|
+
def create_guild_soundboard_sound(guild_id, reason: nil, **options)
|
|
1620
|
+
@rest.post("/guilds/#{guild_id}/soundboard-sounds", body: options.compact, headers: audit_log_headers(reason))
|
|
1621
|
+
end
|
|
1622
|
+
|
|
1623
|
+
# Modify a guild soundboard sound
|
|
1624
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1625
|
+
# @param sound_id [String, Snowflake] Sound ID
|
|
1626
|
+
# @param reason [String, nil] Audit log reason
|
|
1627
|
+
# @param options [Hash] Soundboard payload
|
|
1628
|
+
# @return [Hash] Soundboard sound payload
|
|
1629
|
+
def modify_guild_soundboard_sound(guild_id, sound_id, reason: nil, **options)
|
|
1630
|
+
@rest.patch("/guilds/#{guild_id}/soundboard-sounds/#{sound_id}", body: options.compact, headers: audit_log_headers(reason))
|
|
1631
|
+
end
|
|
1632
|
+
|
|
1633
|
+
# Delete a guild soundboard sound
|
|
1634
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1635
|
+
# @param sound_id [String, Snowflake] Sound ID
|
|
1636
|
+
# @param reason [String, nil] Audit log reason
|
|
1637
|
+
# @return [void]
|
|
1638
|
+
def delete_guild_soundboard_sound(guild_id, sound_id, reason: nil)
|
|
1639
|
+
@rest.delete("/guilds/#{guild_id}/soundboard-sounds/#{sound_id}", headers: audit_log_headers(reason))
|
|
1640
|
+
end
|
|
1641
|
+
|
|
1642
|
+
# Send a soundboard sound in a voice-connected channel
|
|
1643
|
+
# @param channel_id [String, Snowflake] Voice channel ID
|
|
1644
|
+
# @param sound_id [String, Snowflake] Sound ID
|
|
1645
|
+
# @param source_guild_id [String, Snowflake, nil] Source guild for default/shared sounds
|
|
1646
|
+
# @return [void]
|
|
1647
|
+
def send_soundboard_sound(channel_id, sound_id:, source_guild_id: nil)
|
|
1648
|
+
payload = { sound_id: sound_id.to_s, source_guild_id: source_guild_id&.to_s }.compact
|
|
1649
|
+
@rest.post("/channels/#{channel_id}/send-soundboard-sound", body: payload)
|
|
1650
|
+
end
|
|
1651
|
+
|
|
1652
|
+
# Create guild channel
|
|
664
1653
|
# @param guild_id [String, Snowflake] Guild ID
|
|
665
1654
|
# @param name [String] Channel name
|
|
666
1655
|
# @param type [Integer] Channel type (0=text, 2=voice, 4=category, etc.)
|
|
@@ -702,6 +1691,165 @@ module DiscordRDA
|
|
|
702
1691
|
@rest.post("/channels/#{channel_id}/messages/bulk-delete", body: { messages: message_ids.map(&:to_s) }, headers: headers)
|
|
703
1692
|
end
|
|
704
1693
|
|
|
1694
|
+
# Modify the bot user's profile
|
|
1695
|
+
# @param username [String, nil] New username
|
|
1696
|
+
# @param avatar [File, String, nil] New avatar
|
|
1697
|
+
# @return [User, nil] Updated user
|
|
1698
|
+
def modify_current_user(username: nil, avatar: nil)
|
|
1699
|
+
User.modify_current_user(username: username, avatar: avatar)
|
|
1700
|
+
end
|
|
1701
|
+
|
|
1702
|
+
# Get the current user's guilds
|
|
1703
|
+
# @param limit [Integer] Max guilds to return
|
|
1704
|
+
# @param after [String, Snowflake, nil] Cursor
|
|
1705
|
+
# @param before [String, Snowflake, nil] Cursor
|
|
1706
|
+
# @param with_counts [Boolean] Include approximate counts
|
|
1707
|
+
# @return [Array<Hash>] Partial guild payloads
|
|
1708
|
+
def current_user_guilds(limit: 200, after: nil, before: nil, with_counts: false)
|
|
1709
|
+
User.get_current_user_guilds(limit: limit, after: after&.to_s, before: before&.to_s, with_counts: with_counts)
|
|
1710
|
+
end
|
|
1711
|
+
|
|
1712
|
+
# Get the current user's member object in a guild
|
|
1713
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1714
|
+
# @return [Hash, nil] Member payload
|
|
1715
|
+
def current_user_guild_member(guild_id)
|
|
1716
|
+
User.get_current_user_guild_member(guild_id)
|
|
1717
|
+
end
|
|
1718
|
+
|
|
1719
|
+
# Leave a guild as the current user
|
|
1720
|
+
# @param guild_id [String, Snowflake] Guild ID
|
|
1721
|
+
# @return [void]
|
|
1722
|
+
def leave_guild(guild_id)
|
|
1723
|
+
User.leave_guild(guild_id)
|
|
1724
|
+
end
|
|
1725
|
+
|
|
1726
|
+
# Create a DM channel with a user
|
|
1727
|
+
# @param user_id [String, Snowflake] Target user ID
|
|
1728
|
+
# @return [Channel] DM channel
|
|
1729
|
+
def create_dm(user_id)
|
|
1730
|
+
data = @rest.post('/users/@me/channels', body: { recipient_id: user_id.to_s })
|
|
1731
|
+
Channel.new(data)
|
|
1732
|
+
end
|
|
1733
|
+
|
|
1734
|
+
# Get OAuth2 connections for the current user
|
|
1735
|
+
# @return [Array<Hash>] User connections
|
|
1736
|
+
def current_user_connections
|
|
1737
|
+
User.get_connections
|
|
1738
|
+
end
|
|
1739
|
+
|
|
1740
|
+
# Get application role connection metadata for the current user
|
|
1741
|
+
# @param application_id [String, Snowflake] Application ID
|
|
1742
|
+
# @return [Hash, nil] Role connection payload
|
|
1743
|
+
def application_role_connection(application_id)
|
|
1744
|
+
User.get_application_role_connection(application_id)
|
|
1745
|
+
end
|
|
1746
|
+
|
|
1747
|
+
# Update application role connection metadata for the current user
|
|
1748
|
+
# @param application_id [String, Snowflake] Application ID
|
|
1749
|
+
# @param platform_name [String, nil] Platform name
|
|
1750
|
+
# @param platform_username [String, nil] Platform username
|
|
1751
|
+
# @param metadata [Hash] Metadata payload
|
|
1752
|
+
# @return [Hash, nil] Updated role connection payload
|
|
1753
|
+
def update_application_role_connection(application_id, platform_name: nil, platform_username: nil, metadata: {})
|
|
1754
|
+
User.update_application_role_connection(
|
|
1755
|
+
application_id,
|
|
1756
|
+
platform_name: platform_name,
|
|
1757
|
+
platform_username: platform_username,
|
|
1758
|
+
metadata: metadata
|
|
1759
|
+
)
|
|
1760
|
+
end
|
|
1761
|
+
|
|
1762
|
+
# Get current bot application metadata
|
|
1763
|
+
# @return [Hash] Application payload
|
|
1764
|
+
def application_info
|
|
1765
|
+
@rest.get('/oauth2/applications/@me')
|
|
1766
|
+
end
|
|
1767
|
+
|
|
1768
|
+
# Get current authorization information
|
|
1769
|
+
# @return [Hash] Authorization payload
|
|
1770
|
+
def authorization_info
|
|
1771
|
+
@rest.get('/oauth2/@me')
|
|
1772
|
+
end
|
|
1773
|
+
|
|
1774
|
+
# Get gateway information
|
|
1775
|
+
# @return [Hash] Gateway payload
|
|
1776
|
+
def gateway
|
|
1777
|
+
@rest.get('/gateway')
|
|
1778
|
+
end
|
|
1779
|
+
|
|
1780
|
+
# Get gateway bot information
|
|
1781
|
+
# @return [Hash] Gateway bot payload
|
|
1782
|
+
def gateway_bot
|
|
1783
|
+
@rest.get('/gateway/bot')
|
|
1784
|
+
end
|
|
1785
|
+
|
|
1786
|
+
private
|
|
1787
|
+
|
|
1788
|
+
def application_id_for_rest
|
|
1789
|
+
application_info['id']
|
|
1790
|
+
end
|
|
1791
|
+
|
|
1792
|
+
def normalize_rest_params(options, *snowflake_keys)
|
|
1793
|
+
options.each_with_object({}) do |(key, value), params|
|
|
1794
|
+
next if value.nil?
|
|
1795
|
+
|
|
1796
|
+
params[key] = snowflake_keys.include?(key) ? value.to_s : value
|
|
1797
|
+
end
|
|
1798
|
+
end
|
|
1799
|
+
|
|
1800
|
+
def audit_log_headers(reason)
|
|
1801
|
+
reason ? { 'X-Audit-Log-Reason' => CGI.escape(reason) } : {}
|
|
1802
|
+
end
|
|
1803
|
+
|
|
1804
|
+
def register_application_command(cmd, name:, guild_id: nil)
|
|
1805
|
+
cmd.instance_variable_set(:@application_id, me.id.to_s) rescue nil
|
|
1806
|
+
cmd.instance_variable_set(:@guild_id, guild_id.to_s) if guild_id
|
|
1807
|
+
|
|
1808
|
+
key = guild_id ? "#{name}:#{guild_id}" : name
|
|
1809
|
+
@slash_commands[key] = cmd
|
|
1810
|
+
|
|
1811
|
+
if cmd.application_id
|
|
1812
|
+
if guild_id
|
|
1813
|
+
cmd.create_guild(self, guild_id)
|
|
1814
|
+
else
|
|
1815
|
+
cmd.create_global(self)
|
|
1816
|
+
end
|
|
1817
|
+
end
|
|
1818
|
+
|
|
1819
|
+
@logger.info('Registered application command', name: name, type: cmd.command_type, guild: guild_id || 'global')
|
|
1820
|
+
cmd
|
|
1821
|
+
end
|
|
1822
|
+
|
|
1823
|
+
def configure_entity_apis(client)
|
|
1824
|
+
Message.api = client
|
|
1825
|
+
Interaction.api = client
|
|
1826
|
+
Interaction.supervisor = @supervisor
|
|
1827
|
+
User.api = client
|
|
1828
|
+
Guild.api = client
|
|
1829
|
+
Channel.api = client
|
|
1830
|
+
end
|
|
1831
|
+
|
|
1832
|
+
def restart_gateway_state
|
|
1833
|
+
Array(@restart_state['shards']).each_with_object({}) do |shard, states|
|
|
1834
|
+
states[shard['shard_id']] = shard
|
|
1835
|
+
end
|
|
1836
|
+
end
|
|
1837
|
+
|
|
1838
|
+
def install_signal_handlers
|
|
1839
|
+
return if defined?(@signal_handlers_installed) && @signal_handlers_installed
|
|
1840
|
+
|
|
1841
|
+
%w[INT TERM].each do |signal|
|
|
1842
|
+
Signal.trap(signal) do
|
|
1843
|
+
@logger.info('Received shutdown signal', signal: signal)
|
|
1844
|
+
stop
|
|
1845
|
+
rescue StandardError => e
|
|
1846
|
+
@logger&.error('Failed during signal shutdown', signal: signal, error: e)
|
|
1847
|
+
end
|
|
1848
|
+
end
|
|
1849
|
+
|
|
1850
|
+
@signal_handlers_installed = true
|
|
1851
|
+
end
|
|
1852
|
+
|
|
705
1853
|
def setup_interaction_handlers
|
|
706
1854
|
# Handle slash commands
|
|
707
1855
|
@event_bus.on(:interaction_create) do |event|
|
|
@@ -730,11 +1878,25 @@ module DiscordRDA
|
|
|
730
1878
|
if cmd && cmd.handler
|
|
731
1879
|
@logger.debug('Executing slash command', name: cmd_name, user: interaction.user&.id)
|
|
732
1880
|
begin
|
|
733
|
-
|
|
1881
|
+
@supervisor.execute(
|
|
1882
|
+
"command:#{key}",
|
|
1883
|
+
policy: cmd.execution_policy
|
|
1884
|
+
) do
|
|
1885
|
+
cmd.handler.call(interaction)
|
|
1886
|
+
end
|
|
1887
|
+
rescue ExecutionSupervisor::TimeoutError => e
|
|
1888
|
+
@logger.error('Slash command timeout', command: cmd_name, error: e)
|
|
1889
|
+
interaction.respond(content: 'This command timed out and was stopped.', ephemeral: true) rescue nil
|
|
1890
|
+
rescue ExecutionSupervisor::ConcurrencyLimitError => e
|
|
1891
|
+
@logger.warn('Slash command concurrency limit', command: cmd_name, error: e)
|
|
1892
|
+
interaction.respond(content: 'This command is busy right now. Try again in a moment.', ephemeral: true) rescue nil
|
|
1893
|
+
rescue ExecutionSupervisor::CircuitOpenError => e
|
|
1894
|
+
@logger.warn('Slash command circuit open', command: cmd_name, error: e)
|
|
1895
|
+
interaction.respond(content: 'This command was temporarily disabled after repeated failures.', ephemeral: true) rescue nil
|
|
734
1896
|
rescue => e
|
|
735
1897
|
@logger.error('Slash command error', command: cmd_name, error: e)
|
|
736
|
-
# Send error response
|
|
737
1898
|
interaction.respond(content: "An error occurred while executing this command.", ephemeral: true) rescue nil
|
|
1899
|
+
@error_tracker.capture(e, command: cmd_name, user_id: interaction.user&.id)
|
|
738
1900
|
end
|
|
739
1901
|
else
|
|
740
1902
|
@logger.warn('Unknown slash command', name: cmd_name)
|