discorb 0.11.4 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,619 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
- module Discorb
4
- #
5
- # Represents a user interaction with the bot.
6
- #
7
- class Interaction < DiscordModel
8
- # @return [Discorb::Snowflake] The ID of the interaction.
9
- attr_reader :id
10
- # @return [Discorb::Snowflake] The ID of the application that created the interaction.
11
- attr_reader :application_id
12
- # @return [Symbol] The type of interaction.
13
- attr_reader :type
14
- # @return [Discorb::Member] The member that created the interaction.
15
- attr_reader :member
16
- # @return [Discorb::User] The user that created the interaction.
17
- attr_reader :user
18
- # @return [Integer] The type of interaction.
19
- # @note This is always `1` for now.
20
- attr_reader :version
21
- # @return [String] The token for the interaction.
22
- attr_reader :token
23
-
24
- # @!attribute [r] guild
25
- # @macro client_cache
26
- # @return [Discorb::Guild] The guild the interaction took place in.
27
- # @!attribute [r] channel
28
- # @macro client_cache
29
- # @return [Discorb::Channel] The channel the interaction took place in.
30
- # @!attribute [r] target
31
- # @return [Discorb::User, Discorb::Member] The user or member the interaction took place with.
32
-
33
- @interaction_type = nil
34
- @interaction_name = nil
35
-
36
- # @private
37
- def initialize(client, data)
38
- @client = client
39
- @id = Snowflake.new(data[:id])
40
- @application_id = Snowflake.new(data[:application_id])
41
- @type = self.class.interaction_name
42
- @type_id = self.class.interaction_type
43
- @guild_id = data[:guild_id] && Snowflake.new(data[:guild_id])
44
- @channel_id = data[:channel_id] && Snowflake.new(data[:channel_id])
45
- @member = guild.members[data[:member][:id]] || Member.new(@client, @guild_id, data[:member][:user], data[:member]) if data[:member]
46
- @user = @client.users[data[:user][:id]] || User.new(@client, data[:user]) if data[:user]
47
- @token = data[:token]
48
- @version = data[:version]
49
- @defered = false
50
- @responded = false
51
- _set_data(data[:data])
52
- end
53
-
54
- def guild
55
- @client.guilds[@guild_id]
56
- end
57
-
58
- def channel
59
- @client.channels[@channel_id]
60
- end
61
-
62
- def target
63
- @member || @user
64
- end
65
-
66
- alias fired_by target
67
- alias from target
68
-
69
- def inspect
70
- "#<#{self.class} id=#{@id}>"
71
- end
72
-
73
- class << self
74
- # @private
75
- attr_reader :interaction_type, :interaction_name, :event_name
76
-
77
- # @private
78
- def make_interaction(client, data)
79
- interaction = nil
80
- descendants.each do |klass|
81
- interaction = klass.make_interaction(client, data) if !klass.interaction_type.nil? && klass.interaction_type == data[:type]
82
- end
83
- if interaction.nil?
84
- client.log.warn("Unknown interaction type #{data[:type]}, initialized Interaction")
85
- interaction = Interaction.new(client, data)
86
- end
87
- interaction
88
- end
89
-
90
- # @private
91
- def descendants
92
- ObjectSpace.each_object(Class).select { |klass| klass < self }
93
- end
94
- end
95
-
96
- #
97
- # A module for response with source.
98
- #
99
- module SourceResponse
100
- #
101
- # Response with `DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE`(`5`).
102
- #
103
- # @macro async
104
- # @macro http
105
- #
106
- # @param [Boolean] ephemeral Whether to make the response ephemeral.
107
- #
108
- def defer_source(ephemeral: false)
109
- Async do
110
- @client.http.post("/interactions/#{@id}/#{@token}/callback", {
111
- type: 5,
112
- data: {
113
- flags: (ephemeral ? 1 << 6 : 0),
114
- },
115
- }).wait
116
- @defered = true
117
- end
118
- end
119
-
120
- #
121
- # Response with `CHANNEL_MESSAGE_WITH_SOURCE`(`4`).
122
- #
123
- # @macro async
124
- # @macro http
125
- #
126
- # @param [String] content The content of the response.
127
- # @param [Boolean] tts Whether to send the message as text-to-speech.
128
- # @param [Discorb::Embed] embed The embed to send.
129
- # @param [Array<Discorb::Embed>] embeds The embeds to send. (max: 10)
130
- # @param [Discorb::AllowedMentions] allowed_mentions The allowed mentions to send.
131
- # @param [Array<Discorb::Component>, Array<Array<Discorb::Component>>] components The components to send.
132
- # @param [Boolean] ephemeral Whether to make the response ephemeral.
133
- #
134
- # @return [Discorb::Interaction::SourceResponse::CallbackMessage, Discorb::Webhook::Message] The callback message.
135
- #
136
- def post(content = nil, tts: false, embed: nil, embeds: nil, allowed_mentions: nil, components: nil, ephemeral: false)
137
- Async do
138
- payload = {}
139
- payload[:content] = content if content
140
- payload[:tts] = tts
141
- tmp_embed = if embed
142
- [embed]
143
- elsif embeds
144
- embeds
145
- end
146
- payload[:embeds] = tmp_embed.map(&:to_hash) if tmp_embed
147
- payload[:allowed_mentions] = allowed_mentions ? allowed_mentions.to_hash(@client.allowed_mentions) : @client.allowed_mentions.to_hash
148
- if components
149
- tmp_components = []
150
- tmp_row = []
151
- components.each do |c|
152
- case c
153
- when Array
154
- tmp_components << tmp_row
155
- tmp_row = []
156
- tmp_components << c
157
- when SelectMenu
158
- tmp_components << tmp_row
159
- tmp_row = []
160
- tmp_components << [c]
161
- else
162
- tmp_row << c
163
- end
164
- end
165
- tmp_components << tmp_row
166
- payload[:components] = tmp_components.filter { |c| c.length.positive? }.map { |c| { type: 1, components: c.map(&:to_hash) } }
167
- end
168
- payload[:flags] = (ephemeral ? 1 << 6 : 0)
169
-
170
- ret = if @responded
171
- _resp, data = @client.http.post("/webhooks/#{@application_id}/#{@token}", payload).wait
172
- webhook = Webhook::URLWebhook.new("/webhooks/#{@application_id}/#{@token}")
173
- Webhook::Message.new(webhook, data, @client)
174
- elsif @defered
175
- @client.http.patch("/webhooks/#{@application_id}/#{@token}/messages/@original", payload).wait
176
- CallbackMessage.new(@client, payload, @application_id, @token)
177
- else
178
- @client.http.post("/interactions/#{@id}/#{@token}/callback", { type: 4, data: payload }).wait
179
- CallbackMessage.new(@client, payload, @application_id, @token)
180
- end
181
- @responded = true
182
- ret
183
- end
184
- end
185
-
186
- class CallbackMessage
187
- # @private
188
- def initialize(client, data, application_id, token)
189
- @client = client
190
- @data = data
191
- @application_id = application_id
192
- @token = token
193
- end
194
-
195
- #
196
- # Edits the callback message.
197
- # @macro async
198
- # @macro http
199
- # @macro edit
200
- #
201
- # @param [String] content The new content of the message.
202
- # @param [Discorb::Embed] embed The new embed of the message.
203
- # @param [Array<Discorb::Embed>] embeds The new embeds of the message.
204
- # @param [Array<Discorb::Attachment>] attachments The attachments to remain.
205
- # @param [Discorb::File] file The file to send.
206
- # @param [Array<Discorb::File>] files The files to send.
207
- #
208
- def edit(
209
- content = :unset,
210
- embed: :unset, embeds: :unset,
211
- file: :unset, files: :unset,
212
- attachments: :unset
213
- )
214
- Async do
215
- payload = {}
216
- payload[:content] = content if content != :unset
217
- payload[:embeds] = embed ? [embed.to_hash] : [] if embed != :unset
218
- payload[:embeds] = embeds.map(&:to_hash) if embeds != :unset
219
- payload[:attachments] = attachments.map(&:to_hash) if attachments != :unset
220
- files = [file] if file != :unset
221
- if files == :unset
222
- headers = {
223
- "Content-Type" => "application/json",
224
- }
225
- else
226
- headers, payload = HTTP.multipart(payload, files)
227
- end
228
- @client.http.patch("/webhooks/#{@application_id}/#{@token}/messages/@original", payload, headers: headers).wait
229
- end
230
- end
231
-
232
- alias modify edit
233
-
234
- #
235
- # Deletes the callback message.
236
- # @note This will fail if the message is ephemeral.
237
- #
238
- def delete!
239
- Async do
240
- @client.http.delete("/webhooks/#{@application_id}/#{@token}/messages/@original").wait
241
- end
242
- end
243
- end
244
- end
245
-
246
- #
247
- # A module for response with update.
248
- #
249
- module UpdateResponse
250
- #
251
- # Response with `DEFERRED_UPDATE_MESSAGE`(`6`).
252
- #
253
- # @param [Boolean] ephemeral Whether to make the response ephemeral.
254
- #
255
- def defer_update(ephemeral: false)
256
- Async do
257
- @client.http.post("/interactions/#{@id}/#{@token}/callback", {
258
- type: 6,
259
- data: {
260
- flags: (ephemeral ? 1 << 6 : 0),
261
- },
262
- }).wait
263
- end
264
- end
265
-
266
- #
267
- # Response with `UPDATE_MESSAGE`(`7`).
268
- #
269
- # @macro async
270
- # @macro http
271
- #
272
- # @param [String] content The content of the response.
273
- # @param [Boolean] tts Whether to send the message as text-to-speech.
274
- # @param [Discorb::Embed] embed The embed to send.
275
- # @param [Array<Discorb::Embed>] embeds The embeds to send. (max: 10)
276
- # @param [Discorb::AllowedMentions] allowed_mentions The allowed mentions to send.
277
- # @param [Array<Discorb::Component>, Array<Array<Discorb::Component>>] components The components to send.
278
- # @param [Boolean] ephemeral Whether to make the response ephemeral.
279
- #
280
- def edit(content, tts: false, embed: nil, embeds: nil, allowed_mentions: nil, components: nil, ephemeral: false)
281
- Async do
282
- payload = {}
283
- payload[:content] = content if content
284
- payload[:tts] = tts
285
- tmp_embed = if embed
286
- [embed]
287
- elsif embeds
288
- embeds
289
- end
290
- payload[:embeds] = tmp_embed.map(&:to_hash) if tmp_embed
291
- payload[:allowed_mentions] = allowed_mentions ? allowed_mentions.to_hash(@client.allowed_mentions) : @client.allowed_mentions.to_hash
292
- if components
293
- tmp_components = []
294
- tmp_row = []
295
- components.each do |c|
296
- case c
297
- when Array
298
- tmp_components << tmp_row
299
- tmp_row = []
300
- tmp_components << c
301
- when SelectMenu
302
- tmp_components << tmp_row
303
- tmp_row = []
304
- tmp_components << [c]
305
- else
306
- tmp_row << c
307
- end
308
- end
309
- tmp_components << tmp_row
310
- payload[:components] = tmp_components.filter { |c| c.length.positive? }.map { |c| { type: 1, components: c.map(&:to_hash) } }
311
- end
312
- payload[:flags] = (ephemeral ? 1 << 6 : 0)
313
- @client.http.post("/interactions/#{@id}/#{@token}/callback", { type: 7, data: payload }).wait
314
- end
315
- end
316
- end
317
-
318
- private
319
-
320
- def _set_data(*)
321
- nil
322
- end
323
- end
324
-
325
- #
326
- # Represents a command interaction.
327
- #
328
- class CommandInteraction < Interaction
329
- @interaction_type = 2
330
- @interaction_name = :application_command
331
- include Interaction::SourceResponse
332
-
333
- #
334
- # Represents a slash command interaction.
335
- #
336
- class SlashCommand < CommandInteraction
337
- @command_type = 1
338
-
339
- private
340
-
341
- def _set_data(data)
342
- super
343
- Sync do
344
- name = data[:name]
345
- options = nil
346
- if (option = data[:options]&.first)
347
- case option[:type]
348
- when 1
349
- name += " #{option[:name]}"
350
- options = option[:options]
351
- when 2
352
- name += " #{option[:name]}"
353
- if (option_sub = option[:options]&.first)
354
- if option_sub[:type] == 1
355
- name += " #{option_sub[:name]}"
356
- options = option_sub[:options]
357
- else
358
- options = option[:options]
359
- end
360
- end
361
- else
362
- options = data[:options]
363
- end
364
- end
365
-
366
- unless (command = @client.bottom_commands.find { |c| c.to_s == name && c.type_raw == 1 })
367
- @client.log.warn "Unknown command name #{name}, ignoring"
368
- next
369
- end
370
-
371
- option_map = command.options.map { |k, v| [k.to_s, v[:default]] }.to_h
372
- options ||= []
373
- options.each do |option|
374
- val = case option[:type]
375
- when 3, 4, 5, 10
376
- option[:value]
377
- when 6
378
- guild.members[option[:value]] || guild.fetch_member(option[:value]).wait
379
- when 7
380
- guild.channels[option[:value]] || guild.fetch_channels.wait.find { |channel| channel.id == option[:value] }
381
- when 8
382
- guild.roles[option[:value]] || guild.fetch_roles.wait.find { |role| role.id == option[:value] }
383
- when 9
384
- guild.members[option[:value]] || guild.roles[option[:value]] || guild.fetch_member(option[:value]).wait || guild.fetch_roles.wait.find { |role| role.id == option[:value] }
385
- end
386
- option_map[option[:name]] = val
387
- end
388
-
389
- command.block.call(self, *command.options.map { |k, v| option_map[k.to_s] })
390
- end
391
- end
392
- end
393
-
394
- #
395
- # Represents a user context menu interaction.
396
- #
397
- class UserMenuCommand < CommandInteraction
398
- @command_type = 2
399
-
400
- # @return [Discorb::Member, Discorb::User] The target user.
401
- attr_reader :target
402
-
403
- private
404
-
405
- def _set_data(data)
406
- @target = guild.members[data[:target_id]] || Discorb::Member.new(@client, @guild_id, data[:resolved][:users][data[:target_id].to_sym], data[:resolved][:members][data[:target_id].to_sym])
407
- @client.commands.find { |c| c.name == data[:name] && c.type_raw == 2 }.block.call(self, @target)
408
- end
409
- end
410
-
411
- #
412
- # Represents a message context menu interaction.
413
- #
414
- class MessageMenuCommand < CommandInteraction
415
- @command_type = 3
416
-
417
- # @return [Discorb::Message] The target message.
418
- attr_reader :target
419
-
420
- private
421
-
422
- def _set_data(data)
423
- @target = Message.new(@client, data[:resolved][:messages][data[:target_id].to_sym].merge(guild_id: @guild_id.to_s))
424
- @client.commands.find { |c| c.name == data[:name] && c.type_raw == 3 }.block.call(self, @target)
425
- end
426
- end
427
-
428
- private
429
-
430
- def _set_data(data)
431
- @name = data[:name]
432
- end
433
-
434
- class << self
435
- # @private
436
- attr_reader :command_type
437
-
438
- # @private
439
- def make_interaction(client, data)
440
- nested_classes.each do |klass|
441
- return klass.new(client, data) if !klass.command_type.nil? && klass.command_type == data[:data][:type]
442
- end
443
- client.log.warn("Unknown command type #{data[:type]}, initialized CommandInteraction")
444
- CommandInteraction.new(client, data)
445
- end
446
-
447
- # @private
448
- def nested_classes
449
- constants.select { |c| const_get(c).is_a? Class }.map { |c| const_get(c) }
450
- end
451
- end
452
- end
453
-
454
- #
455
- # Represents auto complete interaction.
456
- #
457
- class AutoComplete < Interaction
458
- @interaction_type = 4
459
- @interaction_name = :auto_complete
460
-
461
- # @private
462
- def _set_data(data)
463
- super
464
- Sync do
465
- name = data[:name]
466
- options = nil
467
- if (option = data[:options]&.first)
468
- case option[:type]
469
- when 1
470
- name += " #{option[:name]}"
471
- options = option[:options]
472
- when 2
473
- name += " #{option[:name]}"
474
- if (option_sub = option[:options]&.first)
475
- if option_sub[:type] == 1
476
- name += " #{option_sub[:name]}"
477
- options = option_sub[:options]
478
- else
479
- options = option[:options]
480
- end
481
- end
482
- else
483
- options = data[:options]
484
- end
485
- end
486
-
487
- unless (command = @client.bottom_commands.find { |c| c.to_s == name && c.type_raw == 1 })
488
- @client.log.warn "Unknown command name #{name}, ignoring"
489
- next
490
- end
491
-
492
- option_map = command.options.map { |k, v| [k.to_s, v[:default]] }.to_h
493
- options ||= []
494
- options.each_with_index do |option|
495
- val = case option[:type]
496
- when 3, 4, 5, 10
497
- option[:value]
498
- when 6
499
- guild.members[option[:value]] || guild.fetch_member(option[:value]).wait
500
- when 7
501
- guild.channels[option[:value]] || guild.fetch_channels.wait.find { |channel| channel.id == option[:value] }
502
- when 8
503
- guild.roles[option[:value]] || guild.fetch_roles.wait.find { |role| role.id == option[:value] }
504
- when 9
505
- guild.members[option[:value]] || guild.roles[option[:value]] || guild.fetch_member(option[:value]).wait || guild.fetch_roles.wait.find { |role| role.id == option[:value] }
506
- end
507
- option_map[option[:name]] = val
508
- end
509
- focused_index = options.find_index { |o| o[:focused] }
510
- val = command.options.values[focused_index][:autocomplete]&.call(self, *command.options.map { |k, v| option_map[k.to_s] })
511
- send_complete_result(val)
512
- end
513
- end
514
-
515
- # @private
516
- def send_complete_result(val)
517
- @client.http.post("/interactions/#{@id}/#{@token}/callback", {
518
- type: 8,
519
- data: {
520
- choices: val.map do |vk, vv|
521
- {
522
- name: vk,
523
- value: vv,
524
- }
525
- end,
526
- },
527
- }).wait
528
- rescue Discorb::NotFoundError
529
- @client.log.warn "Failed to send auto complete result, This may be caused by the suggestion is taking too long (over 3 seconds) to respond", fallback: $stderr
530
- end
531
-
532
- class << self
533
- alias make_interaction new
534
- end
535
- end
536
-
537
- #
538
- # Represents a message component interaction.
539
- # @abstract
540
- #
541
- class MessageComponentInteraction < Interaction
542
- include Interaction::SourceResponse
543
- include Interaction::UpdateResponse
544
- # @return [String] The content of the response.
545
- attr_reader :custom_id
546
- # @return [Discorb::Message] The target message.
547
- attr_reader :message
548
-
549
- @interaction_type = 3
550
- @interaction_name = :message_component
551
-
552
- # @private
553
- def initialize(client, data)
554
- super
555
- @message = Message.new(@client, data[:message].merge({ member: data[:member] }))
556
- end
557
-
558
- class << self
559
- # @private
560
- attr_reader :component_type
561
-
562
- # @private
563
- def make_interaction(client, data)
564
- nested_classes.each do |klass|
565
- return klass.new(client, data) if !klass.component_type.nil? && klass.component_type == data[:data][:component_type]
566
- end
567
- client.log.warn("Unknown component type #{data[:component_type]}, initialized Interaction")
568
- MessageComponentInteraction.new(client, data)
569
- end
570
-
571
- # @private
572
- def nested_classes
573
- constants.select { |c| const_get(c).is_a? Class }.map { |c| const_get(c) }
574
- end
575
- end
576
-
577
- #
578
- # Represents a button interaction.
579
- #
580
- class Button < MessageComponentInteraction
581
- @component_type = 2
582
- @event_name = :button_click
583
- # @return [String] The custom id of the button.
584
- attr_reader :custom_id
585
-
586
- private
587
-
588
- def _set_data(data)
589
- @custom_id = data[:custom_id]
590
- end
591
- end
592
-
593
- #
594
- # Represents a select menu interaction.
595
- #
596
- class SelectMenu < MessageComponentInteraction
597
- @component_type = 3
598
- @event_name = :select_menu_select
599
- # @return [String] The custom id of the select menu.
600
- attr_reader :custom_id
601
- # @return [Array<String>] The selected options.
602
- attr_reader :values
603
-
604
- # @!attribute [r] value
605
- # @return [String] The first selected value.
606
-
607
- def value
608
- @values[0]
609
- end
610
-
611
- private
612
-
613
- def _set_data(data)
614
- @custom_id = data[:custom_id]
615
- @values = data[:values]
616
- end
617
- end
618
- end
1
+ %w[root response command components autocomplete].each do |file|
2
+ require_relative "interaction/#{file}.rb"
619
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: discorb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.4
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - sevenc-nanashi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-10-17 00:00:00.000000000 Z
11
+ date: 2021-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -161,6 +161,11 @@ files:
161
161
  - lib/discorb/integration.rb
162
162
  - lib/discorb/intents.rb
163
163
  - lib/discorb/interaction.rb
164
+ - lib/discorb/interaction/autocomplete.rb
165
+ - lib/discorb/interaction/command.rb
166
+ - lib/discorb/interaction/components.rb
167
+ - lib/discorb/interaction/response.rb
168
+ - lib/discorb/interaction/root.rb
164
169
  - lib/discorb/invite.rb
165
170
  - lib/discorb/log.rb
166
171
  - lib/discorb/member.rb