discordrb 3.3.0 → 3.5.0

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