discordrb 3.4.3 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +44 -18
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -1
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -1
- data/.github/workflows/codeql.yml +65 -0
- data/.markdownlint.json +4 -0
- data/.rubocop.yml +8 -2
- data/CHANGELOG.md +390 -225
- data/LICENSE.txt +1 -1
- data/README.md +37 -25
- data/discordrb-webhooks.gemspec +4 -1
- data/discordrb.gemspec +9 -6
- data/lib/discordrb/api/application.rb +202 -0
- data/lib/discordrb/api/channel.rb +177 -11
- data/lib/discordrb/api/interaction.rb +54 -0
- data/lib/discordrb/api/invite.rb +2 -2
- data/lib/discordrb/api/server.rb +40 -19
- data/lib/discordrb/api/user.rb +8 -3
- data/lib/discordrb/api/webhook.rb +57 -0
- data/lib/discordrb/api.rb +19 -5
- data/lib/discordrb/bot.rb +317 -32
- data/lib/discordrb/cache.rb +27 -22
- data/lib/discordrb/commands/command_bot.rb +6 -4
- data/lib/discordrb/commands/container.rb +1 -1
- data/lib/discordrb/commands/parser.rb +2 -2
- data/lib/discordrb/commands/rate_limiter.rb +1 -1
- data/lib/discordrb/container.rb +132 -3
- data/lib/discordrb/data/attachment.rb +15 -0
- data/lib/discordrb/data/audit_logs.rb +3 -3
- data/lib/discordrb/data/channel.rb +167 -23
- data/lib/discordrb/data/component.rb +229 -0
- data/lib/discordrb/data/integration.rb +42 -3
- data/lib/discordrb/data/interaction.rb +800 -0
- data/lib/discordrb/data/invite.rb +1 -1
- data/lib/discordrb/data/member.rb +108 -33
- data/lib/discordrb/data/message.rb +99 -19
- data/lib/discordrb/data/overwrite.rb +13 -7
- data/lib/discordrb/data/role.rb +58 -1
- data/lib/discordrb/data/server.rb +82 -80
- data/lib/discordrb/data/user.rb +69 -9
- data/lib/discordrb/data/webhook.rb +97 -4
- data/lib/discordrb/data.rb +3 -0
- data/lib/discordrb/errors.rb +44 -3
- data/lib/discordrb/events/channels.rb +1 -1
- data/lib/discordrb/events/interactions.rb +482 -0
- data/lib/discordrb/events/message.rb +9 -6
- data/lib/discordrb/events/presence.rb +21 -14
- data/lib/discordrb/events/reactions.rb +0 -1
- data/lib/discordrb/events/threads.rb +96 -0
- data/lib/discordrb/gateway.rb +30 -17
- data/lib/discordrb/permissions.rb +59 -34
- data/lib/discordrb/version.rb +1 -1
- data/lib/discordrb/voice/encoder.rb +2 -2
- data/lib/discordrb/voice/network.rb +18 -7
- data/lib/discordrb/voice/sodium.rb +3 -1
- data/lib/discordrb/voice/voice_bot.rb +3 -3
- data/lib/discordrb/webhooks.rb +2 -0
- data/lib/discordrb.rb +37 -4
- metadata +48 -14
- data/.codeclimate.yml +0 -16
- data/.travis.yml +0 -32
- data/bin/travis_build_docs.sh +0 -17
@@ -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
|