discordrb 3.4.3 → 3.6.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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +13 -0
  3. data/.devcontainer/devcontainer.json +29 -0
  4. data/.devcontainer/postcreate.sh +4 -0
  5. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -1
  6. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -1
  7. data/.github/workflows/ci.yml +78 -0
  8. data/.github/workflows/codeql.yml +65 -0
  9. data/.github/workflows/deploy.yml +54 -0
  10. data/.github/workflows/release.yml +45 -0
  11. data/.markdownlint.json +4 -0
  12. data/.rubocop.yml +58 -2
  13. data/CHANGELOG.md +485 -225
  14. data/LICENSE.txt +1 -1
  15. data/README.md +38 -26
  16. data/discordrb-webhooks.gemspec +4 -1
  17. data/discordrb.gemspec +18 -10
  18. data/lib/discordrb/api/application.rb +278 -0
  19. data/lib/discordrb/api/channel.rb +222 -18
  20. data/lib/discordrb/api/interaction.rb +63 -0
  21. data/lib/discordrb/api/invite.rb +2 -2
  22. data/lib/discordrb/api/server.rb +123 -66
  23. data/lib/discordrb/api/user.rb +20 -5
  24. data/lib/discordrb/api/webhook.rb +72 -0
  25. data/lib/discordrb/api.rb +35 -25
  26. data/lib/discordrb/bot.rb +437 -66
  27. data/lib/discordrb/cache.rb +41 -22
  28. data/lib/discordrb/commands/command_bot.rb +13 -21
  29. data/lib/discordrb/commands/container.rb +1 -1
  30. data/lib/discordrb/commands/parser.rb +7 -7
  31. data/lib/discordrb/commands/rate_limiter.rb +1 -1
  32. data/lib/discordrb/container.rb +178 -3
  33. data/lib/discordrb/data/activity.rb +1 -1
  34. data/lib/discordrb/data/application.rb +1 -0
  35. data/lib/discordrb/data/attachment.rb +38 -3
  36. data/lib/discordrb/data/audit_logs.rb +3 -3
  37. data/lib/discordrb/data/avatar_decoration.rb +26 -0
  38. data/lib/discordrb/data/call.rb +22 -0
  39. data/lib/discordrb/data/channel.rb +299 -30
  40. data/lib/discordrb/data/collectibles.rb +45 -0
  41. data/lib/discordrb/data/component.rb +229 -0
  42. data/lib/discordrb/data/embed.rb +10 -3
  43. data/lib/discordrb/data/emoji.rb +20 -1
  44. data/lib/discordrb/data/integration.rb +45 -3
  45. data/lib/discordrb/data/interaction.rb +937 -0
  46. data/lib/discordrb/data/invite.rb +1 -1
  47. data/lib/discordrb/data/member.rb +236 -44
  48. data/lib/discordrb/data/message.rb +278 -51
  49. data/lib/discordrb/data/overwrite.rb +15 -7
  50. data/lib/discordrb/data/primary_server.rb +60 -0
  51. data/lib/discordrb/data/profile.rb +2 -7
  52. data/lib/discordrb/data/reaction.rb +2 -1
  53. data/lib/discordrb/data/recipient.rb +1 -1
  54. data/lib/discordrb/data/role.rb +204 -18
  55. data/lib/discordrb/data/server.rb +194 -118
  56. data/lib/discordrb/data/server_preview.rb +68 -0
  57. data/lib/discordrb/data/snapshot.rb +110 -0
  58. data/lib/discordrb/data/user.rb +132 -12
  59. data/lib/discordrb/data/voice_region.rb +1 -0
  60. data/lib/discordrb/data/webhook.rb +99 -9
  61. data/lib/discordrb/data.rb +9 -0
  62. data/lib/discordrb/errors.rb +47 -3
  63. data/lib/discordrb/events/await.rb +1 -1
  64. data/lib/discordrb/events/channels.rb +38 -1
  65. data/lib/discordrb/events/generic.rb +2 -0
  66. data/lib/discordrb/events/guilds.rb +6 -1
  67. data/lib/discordrb/events/interactions.rb +575 -0
  68. data/lib/discordrb/events/invites.rb +2 -0
  69. data/lib/discordrb/events/members.rb +19 -2
  70. data/lib/discordrb/events/message.rb +42 -8
  71. data/lib/discordrb/events/presence.rb +23 -14
  72. data/lib/discordrb/events/raw.rb +1 -0
  73. data/lib/discordrb/events/reactions.rb +2 -1
  74. data/lib/discordrb/events/roles.rb +2 -0
  75. data/lib/discordrb/events/threads.rb +100 -0
  76. data/lib/discordrb/events/typing.rb +1 -0
  77. data/lib/discordrb/events/voice_server_update.rb +1 -0
  78. data/lib/discordrb/events/voice_state_update.rb +1 -0
  79. data/lib/discordrb/events/webhooks.rb +1 -0
  80. data/lib/discordrb/gateway.rb +57 -28
  81. data/lib/discordrb/paginator.rb +3 -3
  82. data/lib/discordrb/permissions.rb +71 -35
  83. data/lib/discordrb/version.rb +1 -1
  84. data/lib/discordrb/voice/encoder.rb +2 -2
  85. data/lib/discordrb/voice/network.rb +18 -7
  86. data/lib/discordrb/voice/sodium.rb +3 -1
  87. data/lib/discordrb/voice/voice_bot.rb +3 -3
  88. data/lib/discordrb/webhooks.rb +2 -0
  89. data/lib/discordrb/websocket.rb +0 -10
  90. data/lib/discordrb.rb +54 -5
  91. metadata +87 -25
  92. data/.circleci/config.yml +0 -126
  93. data/.codeclimate.yml +0 -16
  94. data/.travis.yml +0 -32
  95. data/bin/travis_build_docs.sh +0 -17
@@ -0,0 +1,937 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'discordrb/webhooks'
4
+
5
+ module Discordrb
6
+ # Base class for interaction objects.
7
+ class Interaction
8
+ # Interaction types.
9
+ # @see https://discord.com/developers/docs/interactions/slash-commands#interaction-interactiontype
10
+ TYPES = {
11
+ ping: 1,
12
+ command: 2,
13
+ component: 3,
14
+ autocomplete: 4,
15
+ modal_submit: 5
16
+ }.freeze
17
+
18
+ # Interaction response types.
19
+ # @see https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactioncallbacktype
20
+ CALLBACK_TYPES = {
21
+ pong: 1,
22
+ channel_message: 4,
23
+ deferred_message: 5,
24
+ deferred_update: 6,
25
+ update_message: 7,
26
+ autocomplete: 8,
27
+ modal: 9
28
+ }.freeze
29
+
30
+ # @return [User, Member] The user that initiated the interaction.
31
+ attr_reader :user
32
+
33
+ # @return [Integer, nil] The ID of the server this interaction originates from.
34
+ attr_reader :server_id
35
+
36
+ # @return [Integer] The ID of the channel this interaction originates from.
37
+ attr_reader :channel_id
38
+
39
+ # @return [Integer] The ID of this interaction.
40
+ attr_reader :id
41
+
42
+ # @return [Integer] The ID of the application associated with this interaction.
43
+ attr_reader :application_id
44
+
45
+ # @return [String] The interaction token.
46
+ attr_reader :token
47
+
48
+ # @!visibility private
49
+ # @return [Integer] Currently pointless
50
+ attr_reader :version
51
+
52
+ # @return [Integer] The type of this interaction.
53
+ # @see TYPES
54
+ attr_reader :type
55
+
56
+ # @return [Hash] The interaction data.
57
+ attr_reader :data
58
+
59
+ # @return [Array<ActionRow>]
60
+ attr_reader :components
61
+
62
+ # @!visibility private
63
+ def initialize(data, bot)
64
+ @bot = bot
65
+
66
+ @id = data['id'].to_i
67
+ @application_id = data['application_id'].to_i
68
+ @type = data['type']
69
+ @message = data['message']
70
+ @data = data['data']
71
+ @server_id = data['guild_id']&.to_i
72
+ @channel_id = data['channel_id']&.to_i
73
+ @user = if data['member']
74
+ data['member']['guild_id'] = @server_id
75
+ Discordrb::Member.new(data['member'], bot.servers[@server_id], bot)
76
+ else
77
+ bot.ensure_user(data['user'])
78
+ end
79
+ @token = data['token']
80
+ @version = data['version']
81
+ @components = @data['components']&.map { |component| Components.from_data(component, @bot) }&.compact || []
82
+ end
83
+
84
+ # Respond to the creation of this interaction. An interaction must be responded to or deferred,
85
+ # The response may be modified with {Interaction#edit_response} or deleted with {Interaction#delete_response}.
86
+ # Further messages can be sent with {Interaction#send_message}.
87
+ # @param content [String] The content of the message.
88
+ # @param tts [true, false]
89
+ # @param embeds [Array<Hash, Webhooks::Embed>] The embeds for the message.
90
+ # @param allowed_mentions [Hash, AllowedMentions] Mentions that can ping on this message.
91
+ # @param flags [Integer] Message flags.
92
+ # @param ephemeral [true, false] Whether this message should only be visible to the interaction initiator.
93
+ # @param wait [true, false] Whether this method should return a Message object of the interaction response.
94
+ # @param components [Array<#to_h>] An array of components.
95
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`.
96
+ # @yieldparam builder [Webhooks::Builder] An optional message builder. Arguments passed to the method overwrite builder data.
97
+ # @yieldparam view [Webhooks::View] A builder for creating interaction components.
98
+ def respond(content: nil, tts: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: nil, wait: false, components: nil, attachments: nil)
99
+ flags |= 1 << 6 if ephemeral
100
+
101
+ builder = Discordrb::Webhooks::Builder.new
102
+ view = Discordrb::Webhooks::View.new
103
+
104
+ # Set builder defaults from parameters
105
+ prepare_builder(builder, content, embeds, allowed_mentions)
106
+ yield(builder, view) if block_given?
107
+
108
+ components ||= view
109
+ data = builder.to_json_hash
110
+
111
+ Discordrb::API::Interaction.create_interaction_response(@token, @id, CALLBACK_TYPES[:channel_message], data[:content], tts, data[:embeds], data[:allowed_mentions], flags, components.to_a, attachments)
112
+
113
+ return unless wait
114
+
115
+ response = Discordrb::API::Interaction.get_original_interaction_response(@token, @application_id)
116
+ Interactions::Message.new(JSON.parse(response), @bot, @interaction)
117
+ end
118
+
119
+ # Defer an interaction, setting a temporary response that can be later overriden by {Interaction#send_message}.
120
+ # This method is used when you want to use a single message for your response but require additional processing time, or to simply ack
121
+ # an interaction so an error is not displayed.
122
+ # @param flags [Integer] Message flags.
123
+ # @param ephemeral [true, false] Whether this message should only be visible to the interaction initiator.
124
+ def defer(flags: 0, ephemeral: true)
125
+ flags |= 1 << 6 if ephemeral
126
+
127
+ Discordrb::API::Interaction.create_interaction_response(@token, @id, CALLBACK_TYPES[:deferred_message], nil, nil, nil, nil, flags)
128
+ nil
129
+ end
130
+
131
+ # Defer an update to an interaction. This is can only currently used by Button interactions.
132
+ def defer_update
133
+ Discordrb::API::Interaction.create_interaction_response(@token, @id, CALLBACK_TYPES[:deferred_update])
134
+ end
135
+
136
+ # Create a modal as a response.
137
+ # @param title [String] The title of the modal being shown.
138
+ # @param custom_id [String] The custom_id used to identify the modal and store data.
139
+ # @param components [Array<Component, Hash>, nil] An array of components. These can be defined through the block as well.
140
+ # @yieldparam [Discordrb::Webhooks::Modal] A builder for the modal's components.
141
+ def show_modal(title:, custom_id:, components: nil)
142
+ if block_given?
143
+ modal_builder = Discordrb::Webhooks::Modal.new
144
+ yield modal_builder
145
+
146
+ components = modal_builder.to_a
147
+ end
148
+
149
+ Discordrb::API::Interaction.create_interaction_modal_response(@token, @id, custom_id, title, components.to_a) unless type == Interaction::TYPES[:modal_submit]
150
+ nil
151
+ end
152
+
153
+ # Respond to the creation of this interaction. An interaction must be responded to or deferred,
154
+ # The response may be modified with {Interaction#edit_response} or deleted with {Interaction#delete_response}.
155
+ # Further messages can be sent with {Interaction#send_message}.
156
+ # @param content [String] The content of the message.
157
+ # @param tts [true, false]
158
+ # @param embeds [Array<Hash, Webhooks::Embed>] The embeds for the message.
159
+ # @param allowed_mentions [Hash, AllowedMentions] Mentions that can ping on this message.
160
+ # @param flags [Integer] Message flags.
161
+ # @param ephemeral [true, false] Whether this message should only be visible to the interaction initiator.
162
+ # @param wait [true, false] Whether this method should return a Message object of the interaction response.
163
+ # @param components [Array<#to_h>] An array of components.
164
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`.
165
+ # @yieldparam builder [Webhooks::Builder] An optional message builder. Arguments passed to the method overwrite builder data.
166
+ # @yieldparam view [Webhooks::View] A builder for creating interaction components.
167
+ def update_message(content: nil, tts: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: nil, wait: false, components: nil, attachments: nil)
168
+ flags |= 1 << 6 if ephemeral
169
+
170
+ builder = Discordrb::Webhooks::Builder.new
171
+ view = Discordrb::Webhooks::View.new
172
+
173
+ prepare_builder(builder, content, embeds, allowed_mentions)
174
+ yield(builder, view) if block_given?
175
+
176
+ components ||= view
177
+ data = builder.to_json_hash
178
+
179
+ Discordrb::API::Interaction.create_interaction_response(@token, @id, CALLBACK_TYPES[:update_message], data[:content], tts, data[:embeds], data[:allowed_mentions], flags, components.to_a, attachments)
180
+
181
+ return unless wait
182
+
183
+ response = Discordrb::API::Interaction.get_original_interaction_response(@token, @application_id)
184
+ Interactions::Message.new(JSON.parse(response), @bot, @interaction)
185
+ end
186
+
187
+ # Edit the original response to this interaction.
188
+ # @param content [String] The content of the message.
189
+ # @param embeds [Array<Hash, Webhooks::Embed>] The embeds for the message.
190
+ # @param allowed_mentions [Hash, AllowedMentions] Mentions that can ping on this message.
191
+ # @param components [Array<#to_h>] An array of components.
192
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`.
193
+ # @return [InteractionMessage] The updated response message.
194
+ # @yieldparam builder [Webhooks::Builder] An optional message builder. Arguments passed to the method overwrite builder data.
195
+ def edit_response(content: nil, embeds: nil, allowed_mentions: nil, components: nil, attachments: nil)
196
+ builder = Discordrb::Webhooks::Builder.new
197
+ view = Discordrb::Webhooks::View.new
198
+
199
+ prepare_builder(builder, content, embeds, allowed_mentions)
200
+ yield(builder, view) if block_given?
201
+
202
+ components ||= view
203
+ data = builder.to_json_hash
204
+ resp = Discordrb::API::Interaction.edit_original_interaction_response(@token, @application_id, data[:content], data[:embeds], data[:allowed_mentions], components.to_a, attachments)
205
+
206
+ Interactions::Message.new(JSON.parse(resp), @bot, @interaction)
207
+ end
208
+
209
+ # Delete the original interaction response.
210
+ def delete_response
211
+ Discordrb::API::Interaction.delete_original_interaction_response(@token, @application_id)
212
+ end
213
+
214
+ # @param content [String] The content of the message.
215
+ # @param tts [true, false]
216
+ # @param embeds [Array<Hash, Webhooks::Embed>] The embeds for the message.
217
+ # @param allowed_mentions [Hash, AllowedMentions] Mentions that can ping on this message.
218
+ # @param flags [Integer] Message flags.
219
+ # @param ephemeral [true, false] Whether this message should only be visible to the interaction initiator.
220
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`.
221
+ # @yieldparam builder [Webhooks::Builder] An optional message builder. Arguments passed to the method overwrite builder data.
222
+ def send_message(content: nil, embeds: nil, tts: false, allowed_mentions: nil, flags: 0, ephemeral: false, components: nil, attachments: nil)
223
+ flags |= 64 if ephemeral
224
+
225
+ builder = Discordrb::Webhooks::Builder.new
226
+ view = Discordrb::Webhooks::View.new
227
+
228
+ prepare_builder(builder, content, embeds, allowed_mentions)
229
+ yield builder, view if block_given?
230
+
231
+ components ||= view
232
+ data = builder.to_json_hash
233
+
234
+ resp = Discordrb::API::Webhook.token_execute_webhook(
235
+ @token, @application_id, true, data[:content], nil, nil, tts, nil, data[:embeds], data[:allowed_mentions], flags, components.to_a, attachments
236
+ )
237
+ Interactions::Message.new(JSON.parse(resp), @bot, @interaction)
238
+ end
239
+
240
+ # @param message [String, Integer, InteractionMessage, Message] The message created by this interaction to be edited.
241
+ # @param content [String] The message content.
242
+ # @param embeds [Array<Hash, Webhooks::Embed>] The embeds for the message.
243
+ # @param allowed_mentions [Hash, AllowedMentions] Mentions that can ping on this message.
244
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`.
245
+ # @yieldparam builder [Webhooks::Builder] An optional message builder. Arguments passed to the method overwrite builder data.
246
+ def edit_message(message, content: nil, embeds: nil, allowed_mentions: nil, components: nil, attachments: nil)
247
+ builder = Discordrb::Webhooks::Builder.new
248
+ view = Discordrb::Webhooks::View.new
249
+
250
+ prepare_builder(builder, content, embeds, allowed_mentions)
251
+ yield builder, view if block_given?
252
+
253
+ components ||= view
254
+ data = builder.to_json_hash
255
+
256
+ resp = Discordrb::API::Webhook.token_edit_message(
257
+ @token, @application_id, message.resolve_id, data[:content], data[:embeds], data[:allowed_mentions], components.to_a, attachments
258
+ )
259
+ Interactions::Message.new(JSON.parse(resp), @bot, @interaction)
260
+ end
261
+
262
+ # @param message [Integer, String, InteractionMessage, Message] The message created by this interaction to be deleted.
263
+ def delete_message(message)
264
+ Discordrb::API::Webhook.token_delete_message(@token, @application_id, message.resolve_id)
265
+ nil
266
+ end
267
+
268
+ # Show autocomplete choices as a response.
269
+ # @param choices [Array<Hash>, Hash] Array of autocomplete choices to show the user.
270
+ def show_autocomplete_choices(choices)
271
+ choices = choices.map { |name, value| { name: name, value: value } } unless choices.is_a?(Array)
272
+ Discordrb::API::Interaction.create_interaction_response(@token, @id, CALLBACK_TYPES[:autocomplete], nil, nil, nil, nil, nil, nil, nil, choices)
273
+ nil
274
+ end
275
+
276
+ # @return [Server, nil] This will be nil for interactions that occur in DM channels or servers where the bot
277
+ # does not have the `bot` scope.
278
+ def server
279
+ @bot.server(@server_id)
280
+ end
281
+
282
+ # @return [Channel, nil]
283
+ # @raise [Errors::NoPermission] When the bot is not in the server associated with this interaction.
284
+ def channel
285
+ @bot.channel(@channel_id)
286
+ end
287
+
288
+ # @return [Hash, nil] Returns the button that triggered this interaction if applicable, otherwise nil
289
+ def button
290
+ return unless @type == TYPES[:component]
291
+
292
+ @message['components'].each do |row|
293
+ Components::ActionRow.new(row, @bot).buttons.each do |button|
294
+ return button if button.custom_id == @data['custom_id']
295
+ end
296
+ end
297
+ end
298
+
299
+ # @return [Array<TextInput>]
300
+ def text_inputs
301
+ @components&.select { |component| component.is_a? TextInput } | []
302
+ end
303
+
304
+ # @return [TextInput, Button, SelectMenu]
305
+ def get_component(custom_id)
306
+ top_level = @components.flat_map(&:components) || []
307
+ message_level = (@message.instance_of?(Hash) ? Message.new(@message, @bot) : @message)&.components&.flat_map(&:components) || []
308
+ components = top_level.concat(message_level)
309
+ components.find { |component| component.custom_id == custom_id }
310
+ end
311
+
312
+ private
313
+
314
+ # Set builder defaults from parameters
315
+ # @param builder [Discordrb::Webhooks::Builder]
316
+ # @param content [String, nil]
317
+ # @param embeds [Array<Hash, Discordrb::Webhooks::Embed>, nil]
318
+ # @param allowed_mentions [AllowedMentions, Hash, nil]
319
+ def prepare_builder(builder, content, embeds, allowed_mentions)
320
+ builder.content = content
321
+ builder.allowed_mentions = allowed_mentions
322
+ embeds&.each { |embed| builder << embed }
323
+ end
324
+ end
325
+
326
+ # An ApplicationCommand for slash commands.
327
+ class ApplicationCommand
328
+ # Command types. `chat_input` is a command that appears in the text input field. `user` and `message` types appear as context menus
329
+ # for the respective resource.
330
+ TYPES = {
331
+ chat_input: 1,
332
+ user: 2,
333
+ message: 3
334
+ }.freeze
335
+
336
+ # @return [Integer]
337
+ attr_reader :application_id
338
+
339
+ # @return [Integer, nil]
340
+ attr_reader :server_id
341
+
342
+ # @return [String]
343
+ attr_reader :name
344
+
345
+ # @return [String]
346
+ attr_reader :description
347
+
348
+ # @return [true, false]
349
+ attr_reader :default_permission
350
+
351
+ # @return [Hash]
352
+ attr_reader :options
353
+
354
+ # @return [Integer]
355
+ attr_reader :id
356
+
357
+ # @return [true, false]
358
+ attr_reader :nsfw
359
+
360
+ # @!visibility private
361
+ def initialize(data, bot, server_id = nil)
362
+ @bot = bot
363
+ @id = data['id'].to_i
364
+ @application_id = data['application_id'].to_i
365
+ @server_id = server_id&.to_i
366
+
367
+ @name = data['name']
368
+ @description = data['description']
369
+ @default_permission = data['default_permission']
370
+ @options = data['options']
371
+ @nsfw = data['nsfw'] || false
372
+ end
373
+
374
+ # @param subcommand [String, nil] The subcommand to mention.
375
+ # @param subcommand_group [String, nil] The subcommand group to mention.
376
+ # @return [String] the layout to mention it in a message
377
+ def mention(subcommand_group: nil, subcommand: nil)
378
+ if subcommand_group && subcommand
379
+ "</#{name} #{subcommand_group} #{subcommand}:#{id}>"
380
+ elsif subcommand_group
381
+ "</#{name} #{subcommand_group}:#{id}>"
382
+ elsif subcommand
383
+ "</#{name} #{subcommand}:#{id}>"
384
+ else
385
+ "</#{name}:#{id}>"
386
+ end
387
+ end
388
+
389
+ alias_method :to_s, :mention
390
+
391
+ # @param name [String] The name to use for this command.
392
+ # @param description [String] The description of this command.
393
+ # @param default_permission [true, false] Whether this command is available with default permissions.
394
+ # @param nsfw [true, false] Whether this command should be marked as age-restricted.
395
+ # @yieldparam (see Bot#edit_application_command)
396
+ # @return (see Bot#edit_application_command)
397
+ def edit(name: nil, description: nil, default_permission: nil, nsfw: nil, &block)
398
+ @bot.edit_application_command(@id, server_id: @server_id, name: name, description: description, default_permission: default_permission, nsfw: nsfw, &block)
399
+ end
400
+
401
+ # Delete this application command.
402
+ # @return (see Bot#delete_application_command)
403
+ def delete
404
+ @bot.delete_application_command(@id, server_id: @server_id)
405
+ end
406
+
407
+ # Get the permission configuration for the this application command on a specific server.
408
+ # @param server_id [Integer, String, nil] The ID of the server to fetch command permissions for.
409
+ # @return [Array<Permission>] the permissions for this application command in the given server.
410
+ def permissions(server_id: nil)
411
+ raise ArgumentError, 'A server ID must be provided for global application commands' if @server_id.nil? && server_id.nil?
412
+
413
+ response = JSON.parse(API::Application.get_application_command_permissions(@bot.token, @bot.profile.id, @server_id || server_id&.resolve_id, @id))
414
+ response['permissions'].map { |permission| Permission.new(permission, response, @bot) }
415
+ rescue Discordrb::Errors::UnknownError
416
+ # If there aren't any explicit overwrites configured for the command, the response is a 400.
417
+ []
418
+ end
419
+
420
+ # An application command permission for a channel, member, or a role.
421
+ class Permission
422
+ # Map of permission types.
423
+ TYPES = {
424
+ role: 1,
425
+ member: 2,
426
+ channel: 3
427
+ }.freeze
428
+
429
+ # @return [Integer] the type of this permission.
430
+ # @see TYPES
431
+ attr_reader :type
432
+
433
+ # @return [Integer] the ID of the thing this permission is for.
434
+ attr_reader :target_id
435
+
436
+ # @return [Integer] the ID of the server this permission is for.
437
+ attr_reader :server_id
438
+
439
+ # @!visibility private
440
+ def initialize(data, command, bot)
441
+ @bot = bot
442
+ @type = data['type']
443
+ @target_id = data['id'].to_i
444
+ @overwrite = data['permission']
445
+ @command_id = command['id'].to_i
446
+ @server_id = command['guild_id'].to_i
447
+ @application_id = command['application_id'].to_i
448
+ end
449
+
450
+ # Whether this permission has been allowed, e.g has a green check in the UI.
451
+ # @return [true, false]
452
+ def allowed?
453
+ @overwrite == true
454
+ end
455
+
456
+ # Whether this permission has been denied, e.g has a red check in the UI.
457
+ # @return [true, false]
458
+ def denied?
459
+ @overwrite == false
460
+ end
461
+
462
+ # Whether this permission is applied to the everyone role in the server.
463
+ # @return [true, false]
464
+ def everyone?
465
+ @target_id == @server_id
466
+ end
467
+
468
+ # Whether this permission is the default for all commands that don't
469
+ # contain explicit permission oerwrites.
470
+ # @return [true, false]
471
+ def default?
472
+ @command_id == @application_id
473
+ end
474
+
475
+ # Whether this permission is applied to every channel in the server.
476
+ # @return [true, false]
477
+ def all_channels?
478
+ @target_id == (@server_id - 1)
479
+ end
480
+
481
+ # Get the user, role, or channel(s) that this permission targets.
482
+ # @return [Array<Channel>, Role, Member]
483
+ def target
484
+ case @type
485
+ when TYPES[:role]
486
+ @bot.server(@server_id).role(@target_id)
487
+ when TYPES[:member]
488
+ @bot.server(@server_id).member(@target_id)
489
+ when TYPES[:channel]
490
+ all_channels ? @bot.server(@server_id).channels : [@bot.channel(@target_id)]
491
+ end
492
+ end
493
+
494
+ alias_method :targets, :target
495
+
496
+ # @!method role?
497
+ # @return [true, false] whether this permission is for a role.
498
+ # @!method member?
499
+ # @return [true, false] whether this permission is for a member.
500
+ # @!method channel?
501
+ # @return [true, false] whether this permission is for a channel.
502
+ TYPES.each do |name, value|
503
+ define_method("#{name}?") do
504
+ @type == value
505
+ end
506
+ end
507
+ end
508
+ end
509
+
510
+ # Objects specific to Interactions.
511
+ module Interactions
512
+ # A builder for defining slash commands options.
513
+ class OptionBuilder
514
+ # @!visibility private
515
+ TYPES = {
516
+ subcommand: 1,
517
+ subcommand_group: 2,
518
+ string: 3,
519
+ integer: 4,
520
+ boolean: 5,
521
+ user: 6,
522
+ channel: 7,
523
+ role: 8,
524
+ mentionable: 9,
525
+ number: 10,
526
+ attachment: 11
527
+ }.freeze
528
+
529
+ # Channel types that can be provided to #channel
530
+ CHANNEL_TYPES = {
531
+ text: 0,
532
+ dm: 1,
533
+ voice: 2,
534
+ group_dm: 3,
535
+ category: 4,
536
+ news: 5,
537
+ store: 6,
538
+ news_thread: 10,
539
+ public_thread: 11,
540
+ private_thread: 12,
541
+ stage: 13
542
+ }.freeze
543
+
544
+ # @return [Array<Hash>]
545
+ attr_reader :options
546
+
547
+ # @!visibility private
548
+ def initialize
549
+ @options = []
550
+ end
551
+
552
+ # @param name [String, Symbol] The name of the subcommand.
553
+ # @param description [String] A description of the subcommand.
554
+ # @yieldparam [OptionBuilder]
555
+ # @return (see #option)
556
+ # @example
557
+ # bot.register_application_command(:test, 'Test command') do |cmd|
558
+ # cmd.subcommand(:echo) do |sub|
559
+ # sub.string('message', 'What to echo back', required: true)
560
+ # end
561
+ # end
562
+ def subcommand(name, description)
563
+ builder = OptionBuilder.new
564
+ yield builder if block_given?
565
+
566
+ option(TYPES[:subcommand], name, description, options: builder.to_a)
567
+ end
568
+
569
+ # @param name [String, Symbol] The name of the subcommand group.
570
+ # @param description [String] A description of the subcommand group.
571
+ # @yieldparam [OptionBuilder]
572
+ # @return (see #option)
573
+ # @example
574
+ # bot.register_application_command(:test, 'Test command') do |cmd|
575
+ # cmd.subcommand_group(:fun) do |group|
576
+ # group.subcommand(:8ball) do |sub|
577
+ # sub.string(:question, 'What do you ask the mighty 8ball?')
578
+ # end
579
+ # end
580
+ # end
581
+ def subcommand_group(name, description)
582
+ builder = OptionBuilder.new
583
+ yield builder
584
+
585
+ option(TYPES[:subcommand_group], name, description, options: builder.to_a)
586
+ end
587
+
588
+ # @param name [String, Symbol] The name of the argument.
589
+ # @param description [String] A description of the argument.
590
+ # @param required [true, false] Whether this option must be provided.
591
+ # @param min_length [Integer] A minimum length for option value.
592
+ # @param max_length [Integer] A maximum length for option value.
593
+ # @param choices [Hash, nil] Available choices, mapped as `Name => Value`.
594
+ # @param autocomplete [true, false] Whether this option can dynamically show choices.
595
+ # @return (see #option)
596
+ def string(name, description, required: nil, min_length: nil, max_length: nil, choices: nil, autocomplete: nil)
597
+ option(TYPES[:string], name, description,
598
+ required: required, min_length: min_length, max_length: max_length, choices: choices, autocomplete: autocomplete)
599
+ end
600
+
601
+ # @param name [String, Symbol] The name of the argument.
602
+ # @param description [String] A description of the argument.
603
+ # @param required [true, false] Whether this option must be provided.
604
+ # @param min_value [Integer] A minimum value for option.
605
+ # @param max_value [Integer] A maximum value for option.
606
+ # @param choices [Hash, nil] Available choices, mapped as `Name => Value`.
607
+ # @param autocomplete [true, false] Whether this option can dynamically show choices.
608
+ # @return (see #option)
609
+ def integer(name, description, required: nil, min_value: nil, max_value: nil, choices: nil, autocomplete: nil)
610
+ option(TYPES[:integer], name, description,
611
+ required: required, min_value: min_value, max_value: max_value, choices: choices, autocomplete: autocomplete)
612
+ end
613
+
614
+ # @param name [String, Symbol] The name of the argument.
615
+ # @param description [String] A description of the argument.
616
+ # @param required [true, false] Whether this option must be provided.
617
+ # @return (see #option)
618
+ def boolean(name, description, required: nil)
619
+ option(TYPES[:boolean], name, description, required: required)
620
+ end
621
+
622
+ # @param name [String, Symbol] The name of the argument.
623
+ # @param description [String] A description of the argument.
624
+ # @param required [true, false] Whether this option must be provided.
625
+ # @return (see #option)
626
+ def user(name, description, required: nil)
627
+ option(TYPES[:user], name, description, required: required)
628
+ end
629
+
630
+ # @param name [String, Symbol] The name of the argument.
631
+ # @param description [String] A description of the argument.
632
+ # @param required [true, false] Whether this option must be provided.
633
+ # @param types [Array<Symbol, Integer>] See {CHANNEL_TYPES}
634
+ # @return (see #option)
635
+ def channel(name, description, required: nil, types: nil)
636
+ types = types&.collect { |type| type.is_a?(Numeric) ? type : CHANNEL_TYPES[type] }
637
+ option(TYPES[:channel], name, description, required: required, channel_types: types)
638
+ end
639
+
640
+ # @param name [String, Symbol] The name of the argument.
641
+ # @param description [String] A description of the argument.
642
+ # @param required [true, false] Whether this option must be provided.
643
+ # @return (see #option)
644
+ def role(name, description, required: nil)
645
+ option(TYPES[:role], name, description, required: required)
646
+ end
647
+
648
+ # @param name [String, Symbol] The name of the argument.
649
+ # @param description [String] A description of the argument.
650
+ # @param required [true, false] Whether this option must be provided.
651
+ # @return (see #option)
652
+ def mentionable(name, description, required: nil)
653
+ option(TYPES[:mentionable], name, description, required: required)
654
+ end
655
+
656
+ # @param name [String, Symbol] The name of the argument.
657
+ # @param description [String] A description of the argument.
658
+ # @param required [true, false] Whether this option must be provided.
659
+ # @param min_value [Float] A minimum value for option.
660
+ # @param max_value [Float] A maximum value for option.
661
+ # @param autocomplete [true, false] Whether this option can dynamically show choices.
662
+ # @return (see #option)
663
+ def number(name, description, required: nil, min_value: nil, max_value: nil, choices: nil, autocomplete: nil)
664
+ option(TYPES[:number], name, description,
665
+ required: required, min_value: min_value, max_value: max_value, choices: choices, autocomplete: autocomplete)
666
+ end
667
+
668
+ # @param name [String, Symbol] The name of the argument.
669
+ # @param description [String] A description of the argument.
670
+ # @param required [true, false] Whether this option must be provided.
671
+ # @return (see #option)
672
+ def attachment(name, description, required: nil)
673
+ option(TYPES[:attachment], name, description, required: required)
674
+ end
675
+
676
+ # @!visibility private
677
+ # @param type [Integer] The argument type.
678
+ # @param name [String, Symbol] The name of the argument.
679
+ # @param description [String] A description of the argument.
680
+ # @param required [true, false] Whether this option must be provided.
681
+ # @param min_value [Integer, Float] A minimum value for integer and number options.
682
+ # @param max_value [Integer, Float] A maximum value for integer and number options.
683
+ # @param min_length [Integer] A minimum length for string option value.
684
+ # @param max_length [Integer] A maximum length for string option value.
685
+ # @param channel_types [Array<Integer>] Channel types that can be provides for channel options.
686
+ # @param autocomplete [true, false] Whether this option can dynamically show options.
687
+ # @return Hash
688
+ def option(type, name, description, required: nil, choices: nil, options: nil, min_value: nil, max_value: nil,
689
+ min_length: nil, max_length: nil, channel_types: nil, autocomplete: nil)
690
+ opt = { type: type, name: name, description: description }
691
+ choices = choices.map { |option_name, value| { name: option_name, value: value } } if choices
692
+
693
+ opt.merge!({ required: required, choices: choices, options: options, min_value: min_value,
694
+ max_value: max_value, min_length: min_length, max_length: max_length,
695
+ channel_types: channel_types, autocomplete: autocomplete }.compact)
696
+
697
+ @options << opt
698
+ opt
699
+ end
700
+
701
+ # @return [Array<Hash>]
702
+ def to_a
703
+ @options
704
+ end
705
+ end
706
+
707
+ # Builder for creating server application command permissions.
708
+ # @deprecated This system is being replaced in the near future.
709
+ class PermissionBuilder
710
+ # Role permission type
711
+ ROLE = 1
712
+ # User permission type
713
+ USER = 2
714
+
715
+ # @!visibility hidden
716
+ def initialize
717
+ @permissions = []
718
+ end
719
+
720
+ # Allow a role to use this command.
721
+ # @param role_id [Integer]
722
+ # @return [PermissionBuilder]
723
+ def allow_role(role_id)
724
+ create_entry(role_id, ROLE, true)
725
+ end
726
+
727
+ # Deny a role usage of this command.
728
+ # @param role_id [Integer]
729
+ # @return [PermissionBuilder]
730
+ def deny_role(role_id)
731
+ create_entry(role_id, ROLE, false)
732
+ end
733
+
734
+ # Allow a user to use this command.
735
+ # @param user_id [Integer]
736
+ # @return [PermissionBuilder]
737
+ def allow_user(user_id)
738
+ create_entry(user_id, USER, true)
739
+ end
740
+
741
+ # Deny a user usage of this command.
742
+ # @param user_id [Integer]
743
+ # @return [PermissionBuilder]
744
+ def deny_user(user_id)
745
+ create_entry(user_id, USER, false)
746
+ end
747
+
748
+ # Allow an entity to use this command.
749
+ # @param object [Role, User, Member]
750
+ # @return [PermissionBuilder]
751
+ # @raise [ArgumentError]
752
+ def allow(object)
753
+ case object
754
+ when Discordrb::User, Discordrb::Member
755
+ create_entry(object.id, USER, true)
756
+ when Discordrb::Role
757
+ create_entry(object.id, ROLE, true)
758
+ else
759
+ raise ArgumentError, "Unable to create permission for unknown type: #{object.class}"
760
+ end
761
+ end
762
+
763
+ # Deny an entity usage of this command.
764
+ # @param object [Role, User, Member]
765
+ # @return [PermissionBuilder]
766
+ # @raise [ArgumentError]
767
+ def deny(object)
768
+ case object
769
+ when Discordrb::User, Discordrb::Member
770
+ create_entry(object.id, USER, false)
771
+ when Discordrb::Role
772
+ create_entry(object.id, ROLE, false)
773
+ else
774
+ raise ArgumentError, "Unable to create permission for unknown type: #{object.class}"
775
+ end
776
+ end
777
+
778
+ # @!visibility private
779
+ # @return [Array<Hash>]
780
+ def to_a
781
+ @permissions
782
+ end
783
+
784
+ private
785
+
786
+ def create_entry(id, type, permission)
787
+ @permissions << { id: id, type: type, permission: permission }
788
+ self
789
+ end
790
+ end
791
+
792
+ # A message partial for interactions.
793
+ class Message
794
+ include IDObject
795
+
796
+ # @return [Interaction] The interaction that created this message.
797
+ attr_reader :interaction
798
+
799
+ # @return [String, nil] The content of the message.
800
+ attr_reader :content
801
+
802
+ # @return [true, false] Whether this message is pinned in the channel it belongs to.
803
+ attr_reader :pinned
804
+
805
+ # @return [true, false]
806
+ attr_reader :tts
807
+
808
+ # @return [Time]
809
+ attr_reader :timestamp
810
+
811
+ # @return [Time, nil]
812
+ attr_reader :edited_timestamp
813
+
814
+ # @return [true, false]
815
+ attr_reader :edited
816
+
817
+ # @return [Integer]
818
+ attr_reader :id
819
+
820
+ # @return [User] The user of the application.
821
+ attr_reader :author
822
+
823
+ # @return [Attachment]
824
+ attr_reader :attachments
825
+
826
+ # @return [Array<Embed>]
827
+ attr_reader :embeds
828
+
829
+ # @return [Array<User>]
830
+ attr_reader :mentions
831
+
832
+ # @return [Integer]
833
+ attr_reader :flags
834
+
835
+ # @return [Integer]
836
+ attr_reader :channel_id
837
+
838
+ # @return [Hash, nil]
839
+ attr_reader :message_reference
840
+
841
+ # @return [Array<Component>]
842
+ attr_reader :components
843
+
844
+ # @!visibility private
845
+ def initialize(data, bot, interaction)
846
+ @data = data
847
+ @bot = bot
848
+ @interaction = interaction
849
+ @content = data['content']
850
+ @channel_id = data['channel_id'].to_i
851
+ @pinned = data['pinned']
852
+ @tts = data['tts']
853
+
854
+ @message_reference = data['message_reference']
855
+
856
+ @server_id = data['guild_id']&.to_i
857
+
858
+ @timestamp = Time.parse(data['timestamp']) if data['timestamp']
859
+ @edited_timestamp = data['edited_timestamp'].nil? ? nil : Time.parse(data['edited_timestamp'])
860
+ @edited = !@edited_timestamp.nil?
861
+
862
+ @id = data['id'].to_i
863
+
864
+ @author = bot.ensure_user(data['author'] || data['member']['user'])
865
+
866
+ @attachments = []
867
+ @attachments = data['attachments'].map { |e| Attachment.new(e, self, @bot) } if data['attachments']
868
+
869
+ @embeds = []
870
+ @embeds = data['embeds'].map { |e| Embed.new(e, self) } if data['embeds']
871
+
872
+ @mentions = []
873
+
874
+ data['mentions']&.each do |element|
875
+ @mentions << bot.ensure_user(element)
876
+ end
877
+
878
+ @mention_roles = data['mention_roles']
879
+ @mention_everyone = data['mention_everyone']
880
+ @flags = data['flags']
881
+ @pinned = data['pinned']
882
+ @components = data['components'].map { |component_data| Components.from_data(component_data, @bot) } if data['components']
883
+ end
884
+
885
+ # @return [Member, nil] This will return nil if the bot does not have access to the
886
+ # server the interaction originated in.
887
+ def member
888
+ server&.member(@user.id)
889
+ end
890
+
891
+ # @return [Server, nil] This will return nil if the bot does not have access to the
892
+ # server the interaction originated in.
893
+ def server
894
+ @bot.server(@server_id)
895
+ end
896
+
897
+ # @return [Channel] The channel the interaction originates from.
898
+ # @raise [Errors::NoPermission] When the bot is not in the server associated with this interaction.
899
+ def channel
900
+ @bot.channel(@channel_id)
901
+ end
902
+
903
+ # Respond to this message.
904
+ # @param (see Interaction#send_message)
905
+ # @yieldparam (see Interaction#send_message)
906
+ def respond(content: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: true, components: nil, attachments: nil, &block)
907
+ @interaction.send_message(content: content, embeds: embeds, allowed_mentions: allowed_mentions, flags: flags, ephemeral: ephemeral, components: components, attachments: attachments, &block)
908
+ end
909
+
910
+ # Delete this message.
911
+ def delete
912
+ @interaction.delete_message(@id)
913
+ end
914
+
915
+ # Edit this message's data.
916
+ # @param content (see Interaction#send_message)
917
+ # @param embeds (see Interaction#send_message)
918
+ # @param allowed_mentions (see Interaction#send_message)
919
+ # @yieldparam (see Interaction#send_message)
920
+ def edit(content: nil, embeds: nil, allowed_mentions: nil, components: nil, attachments: nil, &block)
921
+ @interaction.edit_message(@id, content: content, embeds: embeds, allowed_mentions: allowed_mentions, components: components, attachments: attachments, &block)
922
+ end
923
+
924
+ # @return [Discordrb::Message]
925
+ def to_message
926
+ Discordrb::Message.new(@data, @bot)
927
+ end
928
+
929
+ alias_method :message, :to_message
930
+
931
+ # @!visibility private
932
+ def inspect
933
+ "<Interaction::Message content=#{@content.inspect} embeds=#{@embeds.inspect} channel_id=#{@channel_id} server_id=#{@server_id} author=#{@author.inspect}>"
934
+ end
935
+ end
936
+ end
937
+ end