discorb 0.13.4 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +2 -0
  3. data/.github/workflows/codeql-analysis.yml +70 -0
  4. data/.github/workflows/lint-push.yml +18 -0
  5. data/.github/workflows/lint.yml +16 -0
  6. data/.rubocop.yml +70 -0
  7. data/Changelog.md +12 -0
  8. data/Gemfile +7 -3
  9. data/Rakefile +22 -22
  10. data/discorb.gemspec +1 -0
  11. data/examples/commands/bookmarker.rb +2 -1
  12. data/examples/commands/hello.rb +1 -0
  13. data/examples/commands/inspect.rb +3 -2
  14. data/examples/components/authorization_button.rb +2 -1
  15. data/examples/components/select_menu.rb +2 -1
  16. data/examples/extension/main.rb +1 -0
  17. data/examples/extension/message_expander.rb +1 -0
  18. data/examples/simple/eval.rb +3 -2
  19. data/examples/simple/ping_pong.rb +1 -0
  20. data/examples/simple/rolepanel.rb +1 -0
  21. data/examples/simple/wait_for_message.rb +4 -3
  22. data/exe/discorb +8 -7
  23. data/lib/discorb/allowed_mentions.rb +64 -0
  24. data/lib/discorb/app_command/command.rb +274 -0
  25. data/lib/discorb/app_command/handler.rb +168 -0
  26. data/lib/discorb/app_command.rb +2 -426
  27. data/lib/discorb/asset.rb +2 -0
  28. data/lib/discorb/audit_logs.rb +3 -3
  29. data/lib/discorb/channel.rb +19 -4
  30. data/lib/discorb/client.rb +30 -27
  31. data/lib/discorb/common.rb +4 -26
  32. data/lib/discorb/components/button.rb +106 -0
  33. data/lib/discorb/components/select_menu.rb +157 -0
  34. data/lib/discorb/components/text_input.rb +96 -0
  35. data/lib/discorb/components.rb +11 -276
  36. data/lib/discorb/dictionary.rb +3 -0
  37. data/lib/discorb/embed.rb +2 -2
  38. data/lib/discorb/emoji.rb +19 -3
  39. data/lib/discorb/emoji_table.rb +1 -1
  40. data/lib/discorb/error.rb +4 -6
  41. data/lib/discorb/event.rb +9 -7
  42. data/lib/discorb/exe/about.rb +1 -0
  43. data/lib/discorb/exe/irb.rb +4 -3
  44. data/lib/discorb/exe/new.rb +6 -7
  45. data/lib/discorb/exe/run.rb +2 -1
  46. data/lib/discorb/exe/setup.rb +8 -5
  47. data/lib/discorb/exe/show.rb +1 -0
  48. data/lib/discorb/extend.rb +19 -14
  49. data/lib/discorb/extension.rb +5 -1
  50. data/lib/discorb/gateway.rb +28 -30
  51. data/lib/discorb/guild.rb +11 -13
  52. data/lib/discorb/guild_template.rb +2 -2
  53. data/lib/discorb/http.rb +15 -17
  54. data/lib/discorb/integration.rb +1 -1
  55. data/lib/discorb/intents.rb +1 -1
  56. data/lib/discorb/interaction/autocomplete.rb +4 -3
  57. data/lib/discorb/interaction/command.rb +34 -9
  58. data/lib/discorb/interaction/components.rb +5 -2
  59. data/lib/discorb/interaction/modal.rb +33 -0
  60. data/lib/discorb/interaction/response.rb +33 -4
  61. data/lib/discorb/interaction/root.rb +1 -0
  62. data/lib/discorb/interaction.rb +2 -1
  63. data/lib/discorb/log.rb +1 -1
  64. data/lib/discorb/member.rb +1 -3
  65. data/lib/discorb/message.rb +26 -277
  66. data/lib/discorb/message_meta.rb +205 -0
  67. data/lib/discorb/modules.rb +1 -1
  68. data/lib/discorb/permission.rb +2 -2
  69. data/lib/discorb/presence.rb +4 -1
  70. data/lib/discorb/rate_limit.rb +2 -4
  71. data/lib/discorb/user.rb +1 -1
  72. data/lib/discorb/utils/colored_puts.rb +1 -0
  73. data/lib/discorb/voice_state.rb +3 -0
  74. data/lib/discorb/webhook.rb +1 -1
  75. data/lib/discorb.rb +1 -0
  76. data/template-replace/scripts/arrow.rb +1 -0
  77. data/template-replace/scripts/favicon.rb +1 -0
  78. data/template-replace/scripts/index.rb +2 -1
  79. data/template-replace/scripts/locale_ja.rb +5 -4
  80. data/template-replace/scripts/sidebar.rb +1 -0
  81. data/template-replace/scripts/version.rb +7 -10
  82. data/template-replace/scripts/yard_replace.rb +5 -4
  83. metadata +16 -2
@@ -122,7 +122,7 @@ module Discorb
122
122
  # @return [Discorb::User] The user associated with the integration.
123
123
 
124
124
  # @private
125
- def initialize(client, data)
125
+ def initialize(_client, data)
126
126
  @id = Snowflake.new(data[:id])
127
127
  @guild_id = data[:guild_id]
128
128
  @user_id = data[:application_id]
@@ -261,7 +261,6 @@ module Discorb
261
261
  #
262
262
  # Represents a `MESSAGE_UPDATE` event.
263
263
  #
264
-
265
264
  class MessageUpdateEvent < GatewayEvent
266
265
  # @return [Discorb::Message] The message before update.
267
266
  attr_reader :before
@@ -393,6 +392,9 @@ module Discorb
393
392
  end
394
393
  end
395
394
 
395
+ #
396
+ # Represents a `GUILD_INTEGRATIONS_UPDATE` event.
397
+ #
396
398
  class GuildIntegrationsUpdateEvent < GatewayEvent
397
399
  def initialize(client, data)
398
400
  @client = client
@@ -528,20 +530,16 @@ module Discorb
528
530
  module Handler
529
531
  private
530
532
 
531
- def connect_gateway(reconnect, force_close: false)
533
+ def connect_gateway(reconnect)
532
534
  if reconnect
533
535
  @log.info "Reconnecting to gateway..."
534
536
  else
535
537
  @log.info "Connecting to gateway..."
536
538
  end
537
539
  Async do
538
- if @connection
540
+ if @connection && !@connection.closed?
539
541
  Async do
540
- if force_close
541
- @connection.force_close
542
- else
543
- @connection.close
544
- end
542
+ @connection.close
545
543
  end
546
544
  end
547
545
  @http = HTTP.new(self)
@@ -578,7 +576,8 @@ module Discorb
578
576
  Errno::ECONNRESET,
579
577
  IOError => e
580
578
  @log.error "Gateway connection closed accidentally: #{e.class}: #{e.message}"
581
- connect_gateway(true, force_close: true)
579
+ @connection.force_close
580
+ connect_gateway(true)
582
581
  else # should never happen
583
582
  connect_gateway(true)
584
583
  end
@@ -593,13 +592,13 @@ module Discorb
593
592
  when 4014
594
593
  raise ClientError.new("Disallowed intents were specified"), cause: nil
595
594
  when 4002, 4003, 4005, 4007
596
- raise ClientError.new(<<~EOS), cause: e
597
- Disconnected from gateway, probably due to library issues.
598
- #{e.message}
595
+ raise ClientError.new(<<~ERROR), cause: e
596
+ Disconnected from gateway, probably due to library issues.
597
+ #{e.message}
599
598
 
600
- Please report this to the library issue tracker.
601
- https://github.com/discorb-lib/discorb/issues
602
- EOS
599
+ Please report this to the library issue tracker.
600
+ https://github.com/discorb-lib/discorb/issues
601
+ ERROR
603
602
  when 1001
604
603
  @log.info "Gateway closed with code 1001, reconnecting."
605
604
  connect_gateway(true)
@@ -608,8 +607,9 @@ module Discorb
608
607
  @log.debug "#{e.message}"
609
608
  connect_gateway(false)
610
609
  end
611
- rescue => e
610
+ rescue StandardError => e
612
611
  @log.error "Discord WebSocket error: #{e.full_message}"
612
+ @connection.force_close
613
613
  connect_gateway(false)
614
614
  end
615
615
  end
@@ -622,7 +622,7 @@ module Discorb
622
622
  end
623
623
 
624
624
  def handle_gateway(payload, reconnect)
625
- Async do |task|
625
+ Async do |_task|
626
626
  data = payload[:d]
627
627
  @last_s = payload[:s] if payload[:s]
628
628
  @log.debug "Received message with opcode #{payload[:op]} from gateway:"
@@ -648,7 +648,7 @@ module Discorb
648
648
  send_gateway(2, **payload)
649
649
  end
650
650
  when 7
651
- @log.info "Received opcode 7, reconnecting"
651
+ @log.info "Received opcode 7, stopping tasks"
652
652
  @tasks.map(&:stop)
653
653
  when 9
654
654
  @log.warn "Received opcode 9, closed connection"
@@ -673,7 +673,7 @@ module Discorb
673
673
  end
674
674
 
675
675
  def handle_heartbeat
676
- Async do |task|
676
+ Async do |_task|
677
677
  interval = @heartbeat_interval
678
678
  sleep((interval / 1000.0 - 1) * rand)
679
679
  loop do
@@ -700,9 +700,7 @@ module Discorb
700
700
  @session_id = data[:session_id]
701
701
  @user = ClientUser.new(self, data[:user])
702
702
  @uncached_guilds = data[:guilds].map { |g| g[:id] }
703
- if @uncached_guilds == [] or !@intents.guilds
704
- ready
705
- end
703
+ ready if (@uncached_guilds == []) || !@intents.guilds
706
704
  dispatch(:ready)
707
705
  @tasks << handle_heartbeat
708
706
  when "GUILD_CREATE"
@@ -905,7 +903,7 @@ module Discorb
905
903
  when "INTEGRATION_UPDATE"
906
904
  return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
907
905
 
908
- before = Integration.new(self, data, data[:guild_id])
906
+ integration = Integration.new(self, data, data[:guild_id])
909
907
  dispatch(:integration_update, integration)
910
908
  when "INTEGRATION_DELETE"
911
909
  return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
@@ -1096,7 +1094,7 @@ module Discorb
1096
1094
  dispatch(:typing_start, TypingStartEvent.new(self, data))
1097
1095
  when "INTERACTION_CREATE"
1098
1096
  interaction = Interaction.make_interaction(self, data)
1099
- dispatch(:integration_create, interaction)
1097
+ dispatch(:interaction_create, interaction)
1100
1098
 
1101
1099
  dispatch(interaction.class.event_name, interaction)
1102
1100
  when "RESUMED"
@@ -1114,15 +1112,15 @@ module Discorb
1114
1112
  old = event.dup
1115
1113
  event.send(:_set_data, data)
1116
1114
  dispatch(:scheduled_event_update, old, event)
1117
- if old.status != event.status
1115
+ if old.status == event.status
1116
+ dispatch(:scheduled_event_edit, old, event)
1117
+ else
1118
1118
  case event.status
1119
1119
  when :active
1120
1120
  dispatch(:scheduled_event_start, event)
1121
1121
  when :completed
1122
1122
  dispatch(:scheduled_event_end, event)
1123
1123
  end
1124
- else
1125
- dispatch(:scheduled_event_edit, old, event)
1126
1124
  end
1127
1125
  when "GUILD_SCHEDULED_EVENT_DELETE"
1128
1126
  @log.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
@@ -1182,12 +1180,12 @@ module Discorb
1182
1180
  def close
1183
1181
  super
1184
1182
  @closed = true
1185
- rescue
1183
+ rescue StandardError
1186
1184
  force_close
1187
1185
  end
1188
1186
 
1189
1187
  def force_close
1190
- @framer.instance_variable_get(:@stream).close
1188
+ @framer.instance_variable_get(:@stream).instance_variable_get(:@io).instance_variable_get(:@io).instance_variable_get(:@io).close
1191
1189
  @closed = true
1192
1190
  end
1193
1191
 
data/lib/discorb/guild.rb CHANGED
@@ -614,7 +614,7 @@ module Discorb
614
614
  #
615
615
  def fetch_members(limit: 0, after: nil)
616
616
  Async do
617
- unless limit == 0
617
+ unless limit.zero?
618
618
  _resp, data = @client.http.request(Route.new("/guilds/#{@id}/members?#{URI.encode_www_form({ after: after, limit: limit })}", "//guilds/:guild_id/members", :get)).wait
619
619
  next data[:members].map { |m| Member.new(@client, @id, m[:user], m) }
620
620
  end
@@ -625,9 +625,7 @@ module Discorb
625
625
  _resp, data = @client.http.request(Route.new("/guilds/#{@id}/members?#{URI.encode_www_form(params)}", "//guilds/:guild_id/members", :get)).wait
626
626
  ret += data.map { |m| Member.new(@client, @id, m[:user], m) }
627
627
  after = data.last[:user][:id]
628
- if data.length != 1000
629
- break
630
- end
628
+ break if data.length != 1000
631
629
  end
632
630
  ret
633
631
  end
@@ -1099,7 +1097,7 @@ module Discorb
1099
1097
  def iframe(theme: "dark", width: 350, height: 500)
1100
1098
  [
1101
1099
  %(<iframe src="https://canary.discord.com/widget?id=#{@guild_id}&theme=#{theme}" width="#{width}" height="#{height}"),
1102
- %(allowtransparency="true" frameborder="0" sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"></iframe>),
1100
+ %(allowtransparency="true" frameborder="0" sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"></iframe>)
1103
1101
  ].join
1104
1102
  end
1105
1103
  end
@@ -1152,9 +1150,9 @@ module Discorb
1152
1150
  @unavailable = false
1153
1151
  @name = data[:name]
1154
1152
  @members = Discorb::Dictionary.new
1155
- data[:members].each do |m|
1156
- Member.new(@client, @id, m[:user], m)
1157
- end if data[:members]
1153
+ data[:members]&.each do |m|
1154
+ Member.new(@client, @id, m[:user], m)
1155
+ end
1158
1156
  @splash = data[:splash] && Asset.new(self, data[:splash], path: "splashes/#{@id}")
1159
1157
  @discovery_splash = data[:discovery_splash] && Asset.new(self, data[:discovery_splash], path: "discovery-splashes/#{@id}")
1160
1158
  @owner_id = data[:owner_id]
@@ -1200,13 +1198,13 @@ module Discorb
1200
1198
  tmp_channels = data[:channels].filter { |c| !c.key?(:thread_metadata) }.map do |c|
1201
1199
  Channel.make_channel(@client, c.merge({ guild_id: @id }))
1202
1200
  end
1203
- @channels = Dictionary.new(tmp_channels.map { |c| [c.id, c] }.to_h, sort: ->(c) { c[1].position })
1204
- @voice_states = Dictionary.new(data[:voice_states].map { |v| [Snowflake.new(v[:user_id]), VoiceState.new(@client, v.merge({ guild_id: @id }))] }.to_h)
1201
+ @channels = Dictionary.new(tmp_channels.to_h { |c| [c.id, c] }, sort: ->(c) { c[1].position })
1202
+ @voice_states = Dictionary.new(data[:voice_states].to_h { |v| [Snowflake.new(v[:user_id]), VoiceState.new(@client, v.merge({ guild_id: @id }))] })
1205
1203
  @threads = data[:threads] ? data[:threads].map { |t| Channel.make_channel(@client, t) } : []
1206
- @presences = Dictionary.new(data[:presences].map { |pr| [Snowflake.new(pr[:user][:id]), Presence.new(@client, pr)] }.to_h)
1204
+ @presences = Dictionary.new(data[:presences].to_h { |pr| [Snowflake.new(pr[:user][:id]), Presence.new(@client, pr)] })
1207
1205
  @max_presences = data[:max_presences]
1208
- @stage_instances = Dictionary.new(data[:stage_instances].map { |s| [Snowflake.new(s[:id]), StageInstance.new(@client, s)] }.to_h)
1209
- @scheduled_events = Dictionary.new(data[:guild_scheduled_events].map { |s| [Snowflake.new(s[:id]), ScheduledEvent.new(@client, s)] }.to_h)
1206
+ @stage_instances = Dictionary.new(data[:stage_instances].to_h { |s| [Snowflake.new(s[:id]), StageInstance.new(@client, s)] })
1207
+ @scheduled_events = Dictionary.new(data[:guild_scheduled_events].to_h { |s| [Snowflake.new(s[:id]), ScheduledEvent.new(@client, s)] })
1210
1208
  @data.update(data)
1211
1209
  end
1212
1210
  end
@@ -187,9 +187,9 @@ module Discorb
187
187
  @nsfw = data[:nsfw]
188
188
  @rate_limit_per_user = data[:rate_limit_per_user]
189
189
  @parent_id = data[:parent_id]
190
- @permission_overwrites = data[:permission_overwrites].map do |ow|
190
+ @permission_overwrites = data[:permission_overwrites].to_h do |ow|
191
191
  [Snowflake.new(ow[:id]), PermissionOverwrite.new(ow[:allow], ow[:deny])]
192
- end.to_h
192
+ end
193
193
  @type = Discorb::Channel.descendants.find { |c| c.channel_type == data[:type] }
194
194
  end
195
195
  end
data/lib/discorb/http.rb CHANGED
@@ -32,13 +32,13 @@ module Discorb
32
32
  # @raise [Discorb::HTTPError] The request was failed.
33
33
  #
34
34
  def request(path, body = "", headers: nil, audit_log_reason: nil, **kwargs)
35
- Async do |task|
35
+ Async do |_task|
36
36
  @ratelimit_handler.wait(path)
37
- if %i[post patch put].include? path.method
38
- resp = http.send(path.method, get_path(path), get_body(body), get_headers(headers, body, audit_log_reason), **kwargs)
39
- else
40
- resp = http.send(path.method, get_path(path), get_headers(headers, body, audit_log_reason), **kwargs)
41
- end
37
+ resp = if %i[post patch put].include? path.method
38
+ http.send(path.method, get_path(path), get_body(body), get_headers(headers, body, audit_log_reason), **kwargs)
39
+ else
40
+ http.send(path.method, get_path(path), get_headers(headers, body, audit_log_reason), **kwargs)
41
+ end
42
42
  data = get_response_data(resp)
43
43
  @ratelimit_handler.save(path, resp)
44
44
  handle_response(resp, data, path, body, headers, audit_log_reason, kwargs)
@@ -61,12 +61,12 @@ module Discorb
61
61
  #
62
62
  # @raise [Discorb::HTTPError] The request was failed.
63
63
  #
64
- def multipart_request(path, body = "", files, headers: nil, audit_log_reason: nil, **kwargs)
65
- Async do |task|
64
+ def multipart_request(path, body, files, headers: nil, audit_log_reason: nil, **kwargs)
65
+ Async do |_task|
66
66
  @ratelimit_handler.wait(path)
67
67
  req = Net::HTTP.const_get(path.method.to_s.capitalize).new(get_path(path), get_headers(headers, body, audit_log_reason), **kwargs)
68
68
  data = [
69
- ["payload_json", get_body(body)],
69
+ ["payload_json", get_body(body)]
70
70
  ]
71
71
  files&.each_with_index do |file, i|
72
72
  next if file.nil?
@@ -143,15 +143,13 @@ module Discorb
143
143
  begin
144
144
  data = JSON.parse(resp.body, symbolize_names: true)
145
145
  rescue JSON::ParserError, TypeError
146
- if resp.body.nil? || resp.body.empty?
147
- data = nil
148
- else
149
- data = resp.body
150
- end
151
- end
152
- if resp["Via"].nil? && resp.code == "429" && data.is_a?(String)
153
- raise CloudFlareBanError.new(resp, @client)
146
+ data = if resp.body.nil? || resp.body.empty?
147
+ nil
148
+ else
149
+ resp.body
150
+ end
154
151
  end
152
+ raise CloudFlareBanError.new(resp, @client) if resp["Via"].nil? && resp.code == "429" && data.is_a?(String)
155
153
  data
156
154
  end
157
155
 
@@ -44,7 +44,7 @@ module Discorb
44
44
  }
45
45
 
46
46
  # @private
47
- def initialize(client, data, guild_id, no_cache: false)
47
+ def initialize(client, data, guild_id)
48
48
  @client = client
49
49
  @data = data
50
50
  @guild_id = guild_id
@@ -86,7 +86,7 @@ module Discorb
86
86
  if @raw_value.key?(name)
87
87
  @raw_value[name]
88
88
  elsif name.end_with?("=") && @raw_value.key?(name[0..-2].to_sym)
89
- raise ArgumentError, "true/false expected" unless args.is_a? TrueClass or args.is_a?(FalseClass)
89
+ raise ArgumentError, "true/false expected" unless args.is_a?(TrueClass) || args.is_a?(FalseClass)
90
90
 
91
91
  @raw_value[name[0..-2].to_sym] = args
92
92
  else
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Discorb
2
3
  #
3
4
  # Represents auto complete interaction.
@@ -17,10 +18,10 @@ module Discorb
17
18
  next
18
19
  end
19
20
 
20
- option_map = command.options.map { |k, v| [k.to_s, v[:default]] }.to_h
21
- Discorb::CommandInteraction::SlashCommand.modify_option_map(option_map, options, guild)
21
+ option_map = command.options.to_h { |k, v| [k.to_s, v[:default]] }
22
+ Discorb::CommandInteraction::SlashCommand.modify_option_map(option_map, options, guild, {}, {})
22
23
  focused_index = options.find_index { |o| o[:focused] }
23
- val = command.options.values[focused_index][:autocomplete]&.call(self, *command.options.map { |k, v| option_map[k.to_s] })
24
+ val = command.options.values.filter { |option| option[:type] != :attachment }[focused_index][:autocomplete]&.call(self, *command.options.map { |k, _v| option_map[k.to_s] })
24
25
  send_complete_result(val)
25
26
  end
26
27
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Discorb
2
3
  #
3
4
  # Represents a command interaction.
@@ -5,7 +6,8 @@ module Discorb
5
6
  class CommandInteraction < Interaction
6
7
  @interaction_type = 2
7
8
  @interaction_name = :application_command
8
- include Interaction::SourceResponse
9
+ include Interaction::SourceResponder
10
+ include Interaction::ModalResponder
9
11
 
10
12
  #
11
13
  # Represents a slash command interaction.
@@ -25,10 +27,10 @@ module Discorb
25
27
  return
26
28
  end
27
29
 
28
- option_map = command.options.map { |k, v| [k.to_s, v[:default]] }.to_h
29
- SlashCommand.modify_option_map(option_map, options, guild)
30
+ option_map = command.options.to_h { |k, v| [k.to_s, v[:default]] }
31
+ SlashCommand.modify_option_map(option_map, options, guild, @members, @attachments)
30
32
 
31
- command.block.call(self, *command.options.map { |k, v| option_map[k.to_s] })
33
+ command.block.call(self, *command.options.map { |k, _v| option_map[k.to_s] })
32
34
  end
33
35
 
34
36
  class << self
@@ -55,13 +57,13 @@ module Discorb
55
57
  options = data[:options]
56
58
  end
57
59
 
58
- return name, options
60
+ [name, options]
59
61
  end
60
62
 
61
63
  # @private
62
- def modify_option_map(option_map, options, guild)
64
+ def modify_option_map(option_map, options, guild, members, attachments)
63
65
  options ||= []
64
- options.each_with_index do |option|
66
+ options.each do |option|
65
67
  val = case option[:type]
66
68
  when 3, 4, 5, 10
67
69
  option[:value]
@@ -72,7 +74,9 @@ module Discorb
72
74
  when 8
73
75
  guild.roles[option[:value]] || guild.fetch_roles.wait.find { |role| role.id == option[:value] }
74
76
  when 9
75
- guild.members[option[:value]] || guild.roles[option[:value]] || guild.fetch_member(option[:value]).wait || guild.fetch_roles.wait.find { |role| role.id == option[:value] }
77
+ members[option[:value]] || guild.members[option[:value]] || guild.roles[option[:value]] || guild.fetch_member(option[:value]).wait || guild.fetch_roles.wait.find { |role| role.id == option[:value] }
78
+ when 11
79
+ attachments[option[:value]]
76
80
  end
77
81
  option_map[option[:name]] = val
78
82
  end
@@ -92,6 +96,7 @@ module Discorb
92
96
  private
93
97
 
94
98
  def _set_data(data)
99
+ super
95
100
  @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])
96
101
  @client.commands.find { |c| c.name == data[:name] && c.type_raw == 2 }.block.call(self, @target)
97
102
  end
@@ -109,7 +114,8 @@ module Discorb
109
114
  private
110
115
 
111
116
  def _set_data(data)
112
- @target = Message.new(@client, data[:resolved][:messages][data[:target_id].to_sym].merge(guild_id: @guild_id.to_s))
117
+ super
118
+ @target = @messages[data[:target_id]]
113
119
  @client.commands.find { |c| c.name == data[:name] && c.type_raw == 3 }.block.call(self, @target)
114
120
  end
115
121
  end
@@ -117,7 +123,26 @@ module Discorb
117
123
  private
118
124
 
119
125
  def _set_data(data)
126
+ super
120
127
  @name = data[:name]
128
+ @messages, @attachments, @members = {}, {}, {}
129
+
130
+ if data[:resolved]
131
+ data[:resolved][:users]&.each do |id, user|
132
+ @client.users[id] = Discorb::User.new(@client, user)
133
+ end
134
+ data[:resolved][:members]&.each do |id, member|
135
+ @members[id] = Discorb::Member.new(
136
+ @client, @guild_id, data[:resolved][:users][id], member
137
+ )
138
+ end
139
+ data[:resolved][:messages]&.to_h do |id, _message|
140
+ @messages[id.to_i] = Message.new(@client, data[:resolved][:messages][data[:target_id].to_sym].merge(guild_id: @guild_id.to_s)).merge(guild_id: @guild_id.to_s)
141
+ end
142
+ data[:resolved][:attachments]&.to_h do |id, attachment|
143
+ @attachments[id.to_s] = Attachment.new(attachment)
144
+ end
145
+ end
121
146
  end
122
147
 
123
148
  class << self
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Discorb
2
3
 
3
4
  #
@@ -5,8 +6,10 @@ module Discorb
5
6
  # @abstract
6
7
  #
7
8
  class MessageComponentInteraction < Interaction
8
- include Interaction::SourceResponse
9
- include Interaction::UpdateResponse
9
+ include Interaction::SourceResponder
10
+ include Interaction::UpdateResponder
11
+ include Interaction::ModalResponder
12
+
10
13
  # @return [String] The content of the response.
11
14
  attr_reader :custom_id
12
15
  # @return [Discorb::Message] The target message.
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discorb
4
+
5
+ #
6
+ # Represents a modal interaction.
7
+ #
8
+ class ModalInteraction < Interaction
9
+ include Interaction::SourceResponder
10
+
11
+ @interaction_type = 5
12
+ @interaction_name = :modal_submit
13
+ @event_name = :modal_submit
14
+
15
+ # @return [String] The custom id of the modal.
16
+ attr_reader :custom_id
17
+ # @return [{String => String}] The contents of the modal.
18
+ attr_reader :contents
19
+
20
+ private
21
+
22
+ def _set_data(data)
23
+ @custom_id = data[:custom_id]
24
+ @contents = data[:components].to_h do |component|
25
+ [component[:components][0][:custom_id], component[:components][0][:value]]
26
+ end
27
+ end
28
+
29
+ class << self
30
+ alias make_interaction new
31
+ end
32
+ end
33
+ end
@@ -1,9 +1,13 @@
1
+ # frozen_string_literal: true
1
2
  module Discorb
3
+ #
4
+ # Represents an interaction of Discord.
5
+ #
2
6
  class Interaction
3
7
  #
4
8
  # A module for response with source.
5
9
  #
6
- module SourceResponse
10
+ module SourceResponder
7
11
  #
8
12
  # Response with `DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE`(`5`).
9
13
  #
@@ -38,14 +42,14 @@ module Discorb
38
42
  # @param [Array<Discorb::Component>, Array<Array<Discorb::Component>>] components The components to send.
39
43
  # @param [Boolean] ephemeral Whether to make the response ephemeral.
40
44
  #
41
- # @return [Discorb::Interaction::SourceResponse::CallbackMessage, Discorb::Webhook::Message] The callback message.
45
+ # @return [Discorb::Interaction::SourceResponder::CallbackMessage, Discorb::Webhook::Message] The callback message.
42
46
  #
43
47
  def post(content = nil, tts: false, embed: nil, embeds: nil, allowed_mentions: nil, components: nil, ephemeral: false)
44
48
  Async do
45
49
  payload = {}
46
50
  payload[:content] = content if content
47
51
  payload[:tts] = tts
48
- payload[:embeds] = (embeds || [embed])&.map { |e| e&.to_hash }.filter { _1 }
52
+ payload[:embeds] = (embeds || [embed]).map { |e| e&.to_hash }.filter { _1 }
49
53
  payload[:allowed_mentions] = allowed_mentions&.to_hash(@client.allowed_mentions) || @client.allowed_mentions.to_hash
50
54
  payload[:components] = Component.to_payload(components) if components
51
55
  payload[:flags] = (ephemeral ? 1 << 6 : 0)
@@ -66,6 +70,9 @@ module Discorb
66
70
  end
67
71
  end
68
72
 
73
+ #
74
+ # Represents of a callback message of interaction.
75
+ #
69
76
  class CallbackMessage
70
77
  # @private
71
78
  def initialize(client, data, application_id, token)
@@ -127,7 +134,7 @@ module Discorb
127
134
  #
128
135
  # A module for response with update.
129
136
  #
130
- module UpdateResponse
137
+ module UpdateResponder
131
138
  #
132
139
  # Response with `DEFERRED_UPDATE_MESSAGE`(`6`).
133
140
  # @async
@@ -181,6 +188,28 @@ module Discorb
181
188
  end
182
189
  end
183
190
 
191
+ #
192
+ # A module for response with modal.
193
+ #
194
+ module ModalResponder
195
+ #
196
+ # Response with `MODAL`(`9`).
197
+ #
198
+ # @param [String] title The title of the modal.
199
+ # @param [String] custom_id The custom id of the modal.
200
+ # @param [Array<Discorb::TextInput>] components The text inputs to send.
201
+ #
202
+ # @return [Async::Task<void>] The task.
203
+ #
204
+ def show_modal(title, custom_id, components)
205
+ payload = { title: title, custom_id: custom_id, components: Component.to_payload(components) }
206
+ @client.http.request(
207
+ Route.new("/interactions/#{@id}/#{@token}/callback", "//interactions/:interaction_id/:token/callback", :post),
208
+ { type: 9, data: payload }
209
+ ).wait
210
+ end
211
+ end
212
+
184
213
  private
185
214
 
186
215
  def _set_data(*)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Discorb
2
3
  #
3
4
  # Represents a user interaction with the bot.
@@ -1,3 +1,4 @@
1
- %w[root response command components autocomplete].each do |file|
1
+ # frozen_string_literal: true
2
+ %w[root response command components autocomplete modal].each do |file|
2
3
  require_relative "interaction/#{file}.rb"
3
4
  end
data/lib/discorb/log.rb CHANGED
@@ -64,7 +64,7 @@ module Discorb
64
64
 
65
65
  def write_output(name, color, message, fallback)
66
66
  unless @out
67
- fallback.puts(message) if fallback
67
+ fallback&.puts(message)
68
68
 
69
69
  return
70
70
  end
@@ -105,9 +105,7 @@ module Discorb
105
105
  end
106
106
 
107
107
  def permissions
108
- if owner?
109
- return Permission.new((1 << 38) - 1)
110
- end
108
+ return Permission.new((1 << 38) - 1) if owner?
111
109
  roles.map(&:permissions).sum(Permission.new(0))
112
110
  end
113
111