discorb 0.11.4 → 0.12.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.
@@ -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