Class: DiscordRDA::Interaction

Inherits:
Entity
  • Object
show all
Defined in:
lib/discord_rda/interactions/interaction.rb

Overview

Interaction represents a Discord interaction (slash command, button click, etc.) Provides full response handling including messages, modals, and autocomplete.

Constant Summary collapse

TYPES =

Interaction types

{
  ping: 1,
  application_command: 2,
  message_component: 3,
  application_command_autocomplete: 4,
  modal_submit: 5,
  premium_required: 6
}.freeze
CALLBACK_TYPES =

Interaction callback types (response types)

{
  pong: 1,                                    # ACK a Ping
  channel_message_with_source: 4,             # Respond with message
  deferred_channel_message_with_source: 5,      # ACK, edit later
  deferred_update_message: 6,                 # For components, ACK and edit later
  update_message: 7,                           # For components, edit message
  application_command_autocomplete_result: 8,  # Autocomplete choices
  modal: 9,                                    # Respond with modal
  premium_required: 10                         # Require premium
}.freeze
CONTEXT_TYPES =

Interaction context types

{
  guild: 0,
  bot_dm: 1,
  private_channel: 2
}.freeze

Class Attribute Summary collapse

Attributes inherited from Entity

#id

Instance Method Summary collapse

Methods inherited from Entity

#==, attribute, #created_at, from_hash, #hash, #initialize, #inspect, #to_h, #to_json

Constructor Details

This class inherits a constructor from DiscordRDA::Entity

Class Attribute Details

.apiObject

Returns the value of attribute api.



56
57
58
# File 'lib/discord_rda/interactions/interaction.rb', line 56

def api
  @api
end

.supervisorObject

Returns the value of attribute supervisor.



57
58
59
# File 'lib/discord_rda/interactions/interaction.rb', line 57

def supervisor
  @supervisor
end

Instance Method Details

#autocomplete(choices) ⇒ void

This method returns an undefined value.

Respond with autocomplete choices

Parameters:

  • choices (Array<Hash>)

    Choice objects with name and value



314
315
316
317
318
319
320
321
322
323
324
# File 'lib/discord_rda/interactions/interaction.rb', line 314

def autocomplete(choices)
  raise 'API client not configured' unless self.class.api
  raise 'Only available for autocomplete interactions' unless autocomplete?

  body = {
    type: CALLBACK_TYPES[:application_command_autocomplete_result],
    data: { choices: choices }
  }

  self.class.api.post("/interactions/#{id}/#{token}/callback", body: body)
end

#autocomplete?Boolean

Check if this is an autocomplete interaction

Returns:

  • (Boolean)

    True if autocomplete



86
87
88
# File 'lib/discord_rda/interactions/interaction.rb', line 86

def autocomplete?
  type == 4
end

#command?Boolean

Check if this is an application command (slash command)

Returns:

  • (Boolean)

    True if slash command



74
75
76
# File 'lib/discord_rda/interactions/interaction.rb', line 74

def command?
  type == 2
end

#command_dataHash?

Get the command data for application command interactions

Returns:

  • (Hash, nil)

    Command data



135
136
137
138
# File 'lib/discord_rda/interactions/interaction.rb', line 135

def command_data
  return nil unless command?
  @raw_data['data']
end

#command_nameString?

Get the command name

Returns:

  • (String, nil)

    Command name



142
143
144
# File 'lib/discord_rda/interactions/interaction.rb', line 142

def command_name
  command_data&.dig('name')
end

#component?Boolean

Check if this is a message component interaction (button/select)

Returns:

  • (Boolean)

    True if component



80
81
82
# File 'lib/discord_rda/interactions/interaction.rb', line 80

def component?
  type == 3
end

#component_dataHash?

Get component data for message component interactions

Returns:

  • (Hash, nil)

    Component data



174
175
176
177
# File 'lib/discord_rda/interactions/interaction.rb', line 174

def component_data
  return nil unless component?
  @raw_data['data']
end

#component_typeInteger?

Get component type

Returns:

  • (Integer, nil)

    Component type



187
188
189
# File 'lib/discord_rda/interactions/interaction.rb', line 187

def component_type
  component_data&.dig('component_type')
end

#context_typeSymbol

Get context type as symbol

Returns:

  • (Symbol)

    Context type



98
99
100
# File 'lib/discord_rda/interactions/interaction.rb', line 98

def context_type
  CONTEXT_TYPES.key(@raw_data['context']) || :guild
end

#custom_idString?

Get custom ID from component

Returns:

  • (String, nil)

    Custom ID



181
182
183
# File 'lib/discord_rda/interactions/interaction.rb', line 181

def custom_id
  component_data&.dig('custom_id')
end

#defer(ephemeral: false) ⇒ void

This method returns an undefined value.

Defer the response (show “thinking…” state)

Parameters:

  • ephemeral (Boolean) (defaults to: false)

    Whether the eventual response should be ephemeral



261
262
263
264
265
266
267
268
269
270
271
# File 'lib/discord_rda/interactions/interaction.rb', line 261

def defer(ephemeral: false)
  raise 'API client not configured' unless self.class.api

  data = ephemeral ? { flags: 64 } : {}
  body = {
    type: CALLBACK_TYPES[:deferred_channel_message_with_source],
    data: data
  }

  self.class.api.post("/interactions/#{id}/#{token}/callback", body: body)
end

#defer_updatevoid

This method returns an undefined value.

Defer updating the original message (for components)



275
276
277
278
279
280
281
282
283
284
# File 'lib/discord_rda/interactions/interaction.rb', line 275

def defer_update
  raise 'API client not configured' unless self.class.api
  raise 'Only available for component interactions' unless component?

  body = {
    type: CALLBACK_TYPES[:deferred_update_message]
  }

  self.class.api.post("/interactions/#{id}/#{token}/callback", body: body)
end

#delete_followup(message_id) ⇒ void

This method returns an undefined value.

Delete a followup message

Parameters:

  • message_id (String)

    Followup message ID



437
438
439
440
441
# File 'lib/discord_rda/interactions/interaction.rb', line 437

def delete_followup(message_id)
  raise 'API client not configured' unless self.class.api

  self.class.api.delete("/webhooks/#{application_id}/#{token}/messages/#{message_id}")
end

#delete_originalvoid

This method returns an undefined value.

Delete the original interaction response



377
378
379
380
381
# File 'lib/discord_rda/interactions/interaction.rb', line 377

def delete_original
  raise 'API client not configured' unless self.class.api

  self.class.api.delete("/webhooks/#{application_id}/#{token}/messages/@original")
end

#dm_context?Boolean

Check if interaction is from a DM

Returns:

  • (Boolean)

    True if DM context



110
111
112
# File 'lib/discord_rda/interactions/interaction.rb', line 110

def dm_context?
  context_type == :bot_dm || context_type == :private_channel
end

#edit_followup(message_id, content = nil, **options) {|MessageBuilder| ... } ⇒ Message

Edit a followup message

Parameters:

  • message_id (String)

    Followup message ID

  • content (String) (defaults to: nil)

    New content

  • options (Hash)

    Edit options

Yields:

Returns:



419
420
421
422
423
424
425
426
427
428
429
430
431
432
# File 'lib/discord_rda/interactions/interaction.rb', line 419

def edit_followup(message_id, content = nil, **options, &block)
  raise 'API client not configured' unless self.class.api

  payload = { content: content }.merge(options).compact

  if block
    builder = MessageBuilder.new(payload)
    block.call(builder)
    payload = builder.to_h
  end

  data = self.class.api.patch("/webhooks/#{application_id}/#{token}/messages/#{message_id}", body: payload)
  Message.new(data)
end

#edit_original(content = nil, **options) {|MessageBuilder| ... } ⇒ Message

Edit the original interaction response

Parameters:

  • content (String) (defaults to: nil)

    New content

  • options (Hash)

    Edit options

Yields:

Returns:



360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/discord_rda/interactions/interaction.rb', line 360

def edit_original(content = nil, **options, &block)
  raise 'API client not configured' unless self.class.api

  payload = { content: content }.merge(options).compact

  if block
    builder = MessageBuilder.new(payload)
    block.call(builder)
    payload = builder.to_h
  end

  data = self.class.api.patch("/webhooks/#{application_id}/#{token}/messages/@original", body: payload)
  Message.new(data)
end

#focused_optionHash?

Get focused option for autocomplete

Returns:

  • (Hash, nil)

    Focused option data



167
168
169
170
# File 'lib/discord_rda/interactions/interaction.rb', line 167

def focused_option
  return nil unless autocomplete?
  command_data&.dig('options')&.find { |opt| opt['focused'] }
end

#followup(content = nil, **options) {|MessageBuilder| ... } ⇒ Message

Create a followup message

Parameters:

  • content (String) (defaults to: nil)

    Message content

  • options (Hash)

    Message options

Yields:

Returns:



388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/discord_rda/interactions/interaction.rb', line 388

def followup(content = nil, **options, &block)
  raise 'API client not configured' unless self.class.api

  payload = { content: content }.merge(options).compact

  if block
    builder = MessageBuilder.new(payload)
    block.call(builder)
    payload = builder.to_h
  end

  data = self.class.api.post("/webhooks/#{application_id}/#{token}", body: payload)
  Message.new(data)
end

#get_followup(message_id) ⇒ Message

Get a followup message

Parameters:

  • message_id (String)

    Followup message ID

Returns:



406
407
408
409
410
411
# File 'lib/discord_rda/interactions/interaction.rb', line 406

def get_followup(message_id)
  raise 'API client not configured' unless self.class.api

  data = self.class.api.get("/webhooks/#{application_id}/#{token}/messages/#{message_id}")
  Message.new(data)
end

#guild_context?Boolean

Check if interaction is from a guild

Returns:

  • (Boolean)

    True if guild context



104
105
106
# File 'lib/discord_rda/interactions/interaction.rb', line 104

def guild_context?
  context_type == :guild
end

#interaction_typeSymbol

Get interaction type as symbol

Returns:

  • (Symbol)

    Interaction type



62
63
64
# File 'lib/discord_rda/interactions/interaction.rb', line 62

def interaction_type
  TYPES.key(type) || :unknown
end

#memberMember?

Get the guild member who triggered the interaction

Returns:

  • (Member, nil)

    Member entity (nil if not in guild)



128
129
130
131
# File 'lib/discord_rda/interactions/interaction.rb', line 128

def member
  return nil unless @raw_data['member']
  Member.new(@raw_data['member'].merge('guild_id' => guild_id.to_s))
end

This method returns an undefined value.

Respond with a modal

Parameters:

  • custom_id (String)

    Modal custom ID

  • title (String)

    Modal title

  • components (Array<Hash>) (defaults to: nil)

    Modal components (text inputs)

Yields:



332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/discord_rda/interactions/interaction.rb', line 332

def modal(custom_id:, title:, components: nil, &block)
  raise 'API client not configured' unless self.class.api

  data = {
    custom_id: custom_id,
    title: title,
    components: components || []
  }

  if block
    builder = ModalBuilder.new(data)
    block.call(builder)
    data = builder.to_h
  end

  body = {
    type: CALLBACK_TYPES[:modal],
    data: data
  }

  self.class.api.post("/interactions/#{id}/#{token}/callback", body: body)
end

Get modal submit data

Returns:

  • (Hash, nil)

    Modal data with components



199
200
201
202
# File 'lib/discord_rda/interactions/interaction.rb', line 199

def modal_data
  return nil unless modal_submit?
  @raw_data['data']
end

Check if this is a modal submit

Returns:

  • (Boolean)

    True if modal submit



92
93
94
# File 'lib/discord_rda/interactions/interaction.rb', line 92

def modal_submit?
  type == 5
end

Get a modal value by custom_id

Parameters:

  • id (String)

    Component custom_id

Returns:

  • (String, nil)

    Component value



223
224
225
# File 'lib/discord_rda/interactions/interaction.rb', line 223

def modal_value(id)
  modal_values[id.to_s]
end

Get values from modal submission

Returns:

  • (Hash)

    Component custom_id to value mapping



206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/discord_rda/interactions/interaction.rb', line 206

def modal_values
  return {} unless modal_submit?

  values = {}
  modal_data&.dig('components')&.each do |row|
    row['components']&.each do |component|
      if component['custom_id'] && component['value']
        values[component['custom_id']] = component['value']
      end
    end
  end
  values
end

#option(name) ⇒ Object?

Get a specific option value

Parameters:

  • name (String)

    Option name

Returns:

  • (Object, nil)

    Option value



161
162
163
# File 'lib/discord_rda/interactions/interaction.rb', line 161

def option(name)
  options[name.to_s]
end

#optionsHash

Get command options as a hash

Returns:

  • (Hash)

    Option name to value mapping



148
149
150
151
152
153
154
155
156
# File 'lib/discord_rda/interactions/interaction.rb', line 148

def options
  return {} unless command_data && command_data['options']

  opts = {}
  command_data['options'].each do |opt|
    opts[opt['name']] = resolve_option_value(opt)
  end
  opts
end

#original_messageMessage?

Get the original message that triggered this component interaction

Returns:

  • (Message, nil)

    Original message



229
230
231
232
# File 'lib/discord_rda/interactions/interaction.rb', line 229

def original_message
  return nil unless @raw_data['message']
  Message.new(@raw_data['message'])
end

#ping?Boolean

Check if this is a ping interaction

Returns:

  • (Boolean)

    True if ping



68
69
70
# File 'lib/discord_rda/interactions/interaction.rb', line 68

def ping?
  type == 1
end

#premium_requiredvoid

This method returns an undefined value.

Show premium required response



456
457
458
459
460
461
462
463
464
# File 'lib/discord_rda/interactions/interaction.rb', line 456

def premium_required
  raise 'API client not configured' unless self.class.api

  body = {
    type: CALLBACK_TYPES[:premium_required]
  }

  self.class.api.post("/interactions/#{id}/#{token}/callback", body: body)
end

#respond(content = nil, **options) {|MessageBuilder| ... } ⇒ void

This method returns an undefined value.

Respond to the interaction with a message

Parameters:

  • content (String) (defaults to: nil)

    Message content

  • options (Hash)

    Response options (embeds, components, etc.)

Yields:



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/discord_rda/interactions/interaction.rb', line 239

def respond(content = nil, **options, &block)
  raise 'API client not configured' unless self.class.api

  payload = { content: content }.merge(options).compact

  if block
    builder = MessageBuilder.new(payload)
    block.call(builder)
    payload = builder.to_h
  end

  body = {
    type: CALLBACK_TYPES[:channel_message_with_source],
    data: payload
  }

  self.class.api.post("/interactions/#{id}/#{token}/callback", body: body)
end

#run_isolated(ruby_code:, timeout_seconds: 15, memory_limit_mb: nil, env: {}) ⇒ Object



443
444
445
446
447
448
449
450
451
452
# File 'lib/discord_rda/interactions/interaction.rb', line 443

def run_isolated(ruby_code:, timeout_seconds: 15, memory_limit_mb: nil, env: {})
  raise 'Execution supervisor not configured' unless self.class.supervisor

  self.class.supervisor.run_isolated(
    ruby_code: ruby_code,
    timeout_seconds: timeout_seconds,
    memory_limit_mb: memory_limit_mb,
    env: env
  )
end

#selected_valuesArray<String>

Get selected values from select menu

Returns:

  • (Array<String>)

    Selected values



193
194
195
# File 'lib/discord_rda/interactions/interaction.rb', line 193

def selected_values
  component_data&.dig('values') || []
end

#update_message(content = nil, **options) {|MessageBuilder| ... } ⇒ void

This method returns an undefined value.

Update the original message (for components)

Parameters:

  • content (String) (defaults to: nil)

    New content

  • options (Hash)

    Update options

Yields:



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/discord_rda/interactions/interaction.rb', line 291

def update_message(content = nil, **options, &block)
  raise 'API client not configured' unless self.class.api
  raise 'Only available for component interactions' unless component?

  payload = { content: content }.merge(options).compact

  if block
    builder = MessageBuilder.new(payload)
    block.call(builder)
    payload = builder.to_h
  end

  body = {
    type: CALLBACK_TYPES[:update_message],
    data: payload
  }

  self.class.api.post("/interactions/#{id}/#{token}/callback", body: body)
end

#userUser

Get the user who triggered the interaction

Returns:

  • (User)

    User entity



116
117
118
119
120
121
122
123
124
# File 'lib/discord_rda/interactions/interaction.rb', line 116

def user
  if member && member['user']
    User.new(member['user'].merge('member' => member))
  elsif @raw_data['user']
    User.new(@raw_data['user'])
  else
    nil
  end
end