discorb 0.13.4 → 0.15.1

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +2 -0
  3. data/.github/workflows/build_version.yml +1 -1
  4. data/.github/workflows/codeql-analysis.yml +70 -0
  5. data/.github/workflows/lint-push.yml +20 -0
  6. data/.github/workflows/lint.yml +16 -0
  7. data/.rubocop.yml +74 -0
  8. data/Changelog.md +30 -0
  9. data/Gemfile +7 -3
  10. data/Rakefile +28 -22
  11. data/discorb.gemspec +1 -0
  12. data/docs/events.md +50 -0
  13. data/docs/faq.md +8 -8
  14. data/examples/commands/bookmarker.rb +2 -1
  15. data/examples/commands/hello.rb +1 -0
  16. data/examples/commands/inspect.rb +3 -2
  17. data/examples/components/authorization_button.rb +2 -1
  18. data/examples/components/select_menu.rb +2 -1
  19. data/examples/extension/main.rb +1 -0
  20. data/examples/extension/message_expander.rb +1 -0
  21. data/examples/simple/eval.rb +3 -2
  22. data/examples/simple/ping_pong.rb +1 -0
  23. data/examples/simple/rolepanel.rb +1 -0
  24. data/examples/simple/wait_for_message.rb +4 -3
  25. data/exe/discorb +8 -7
  26. data/lib/discorb/allowed_mentions.rb +71 -0
  27. data/lib/discorb/app_command/command.rb +336 -0
  28. data/lib/discorb/app_command/handler.rb +168 -0
  29. data/lib/discorb/app_command.rb +2 -426
  30. data/lib/discorb/application.rb +16 -7
  31. data/lib/discorb/asset.rb +11 -0
  32. data/lib/discorb/{file.rb → attachment.rb} +55 -33
  33. data/lib/discorb/audit_logs.rb +45 -7
  34. data/lib/discorb/channel.rb +65 -15
  35. data/lib/discorb/client.rb +34 -27
  36. data/lib/discorb/common.rb +19 -27
  37. data/lib/discorb/components/button.rb +105 -0
  38. data/lib/discorb/components/select_menu.rb +143 -0
  39. data/lib/discorb/components/text_input.rb +96 -0
  40. data/lib/discorb/components.rb +11 -276
  41. data/lib/discorb/dictionary.rb +5 -0
  42. data/lib/discorb/embed.rb +73 -40
  43. data/lib/discorb/emoji.rb +48 -5
  44. data/lib/discorb/error.rb +9 -5
  45. data/lib/discorb/event.rb +36 -24
  46. data/lib/discorb/exe/about.rb +1 -0
  47. data/lib/discorb/exe/irb.rb +4 -3
  48. data/lib/discorb/exe/new.rb +6 -7
  49. data/lib/discorb/exe/run.rb +2 -1
  50. data/lib/discorb/exe/setup.rb +8 -5
  51. data/lib/discorb/exe/show.rb +1 -0
  52. data/lib/discorb/extend.rb +19 -14
  53. data/lib/discorb/extension.rb +5 -1
  54. data/lib/discorb/gateway.rb +112 -51
  55. data/lib/discorb/gateway_requests.rb +4 -7
  56. data/lib/discorb/guild.rb +73 -41
  57. data/lib/discorb/guild_template.rb +26 -5
  58. data/lib/discorb/http.rb +38 -18
  59. data/lib/discorb/integration.rb +24 -9
  60. data/lib/discorb/intents.rb +16 -11
  61. data/lib/discorb/interaction/autocomplete.rb +6 -5
  62. data/lib/discorb/interaction/command.rb +66 -12
  63. data/lib/discorb/interaction/components.rb +19 -3
  64. data/lib/discorb/interaction/modal.rb +33 -0
  65. data/lib/discorb/interaction/response.rb +45 -4
  66. data/lib/discorb/interaction/root.rb +16 -0
  67. data/lib/discorb/interaction.rb +2 -1
  68. data/lib/discorb/invite.rb +11 -7
  69. data/lib/discorb/log.rb +5 -5
  70. data/lib/discorb/member.rb +22 -3
  71. data/lib/discorb/message.rb +39 -234
  72. data/lib/discorb/message_meta.rb +186 -0
  73. data/lib/discorb/modules.rb +39 -15
  74. data/lib/discorb/permission.rb +16 -7
  75. data/lib/discorb/presence.rb +45 -9
  76. data/lib/discorb/rate_limit.rb +7 -4
  77. data/lib/discorb/reaction.rb +6 -0
  78. data/lib/discorb/role.rb +12 -0
  79. data/lib/discorb/sticker.rb +22 -14
  80. data/lib/discorb/user.rb +12 -1
  81. data/lib/discorb/utils/colored_puts.rb +1 -0
  82. data/lib/discorb/voice_state.rb +23 -2
  83. data/lib/discorb/webhook.rb +54 -3
  84. data/lib/discorb.rb +5 -2
  85. data/sig/discorb.rbs +838 -702
  86. data/template-replace/scripts/arrow.rb +1 -0
  87. data/template-replace/scripts/favicon.rb +1 -0
  88. data/template-replace/scripts/index.rb +2 -1
  89. data/template-replace/scripts/locale_ja.rb +5 -4
  90. data/template-replace/scripts/sidebar.rb +1 -0
  91. data/template-replace/scripts/version.rb +7 -10
  92. data/template-replace/scripts/yard_replace.rb +5 -4
  93. metadata +17 -3
@@ -33,7 +33,13 @@ module Discorb
33
33
  # @return [Discorb::Guild] The guild this template is based on.
34
34
  # @return [nil] Client wasn't able to find the guild this template is based on.
35
35
 
36
+ #
37
+ # Initialize a new template.
36
38
  # @private
39
+ #
40
+ # @param [Discorb::Client] client The client.
41
+ # @param [Hash] data The data from Discord.
42
+ #
37
43
  def initialize(client, data)
38
44
  @client = client
39
45
  _set_data(data)
@@ -120,14 +126,19 @@ module Discorb
120
126
  attr_reader :widget_enabled
121
127
  alias widget_enabled? widget_enabled
122
128
 
129
+ #
130
+ # Initialize a new guild in guild template.
123
131
  # @private
132
+ #
133
+ # @param [Hash] data The data from Discord.
134
+ #
124
135
  def initialize(data)
125
136
  @name = data[:name]
126
137
  @description = data[:description]
127
138
  @region = data[:region]
128
- @verification_level = Discorb::Guild.mfa_levels[data[:verification_level]]
129
- @default_message_notifications = Discorb::Guild.notification_levels[data[:default_message_notifications]]
130
- @explicit_content_filter = Discorb::Guild.explicit_content_filter[data[:explicit_content_filter]]
139
+ @verification_level = Discorb::Guild::MFA_LEVELS[data[:verification_level]]
140
+ @default_message_notifications = Discorb::Guild::NOTIFICATION_LEVELS[data[:default_message_notifications]]
141
+ @explicit_content_filter = Discorb::Guild::EXPLICIT_CONTENT_FILTERS[data[:explicit_content_filter]]
131
142
  @preferred_locale = data[:preferred_locale]
132
143
  @afk_timeout = data[:afk_timeout]
133
144
  @roles = data[:roles].map { |r| Role.new(r) }
@@ -146,7 +157,12 @@ module Discorb
146
157
  # @return [Discorb::Color] The color of the role.
147
158
  attr_reader :color
148
159
 
160
+ #
161
+ # Initialize a new role in guild template.
149
162
  # @private
163
+ #
164
+ # @param [Hash] data The data from Discord.
165
+ #
150
166
  def initialize(data)
151
167
  @name = data[:name]
152
168
  @permissions = Permission.new(data[:permissions])
@@ -177,7 +193,12 @@ module Discorb
177
193
  # @return [Class] The class of the channel.
178
194
  attr_reader :type
179
195
 
196
+ #
197
+ # Initialize a new channel in guild template.
180
198
  # @private
199
+ #
200
+ # @param [Hash] data The data from Discord.
201
+ #
181
202
  def initialize(data)
182
203
  @name = data[:name]
183
204
  @position = data[:position]
@@ -187,9 +208,9 @@ module Discorb
187
208
  @nsfw = data[:nsfw]
188
209
  @rate_limit_per_user = data[:rate_limit_per_user]
189
210
  @parent_id = data[:parent_id]
190
- @permission_overwrites = data[:permission_overwrites].map do |ow|
211
+ @permission_overwrites = data[:permission_overwrites].to_h do |ow|
191
212
  [Snowflake.new(ow[:id]), PermissionOverwrite.new(ow[:allow], ow[:deny])]
192
- end.to_h
213
+ end
193
214
  @type = Discorb::Channel.descendants.find { |c| c.channel_type == data[:type] }
194
215
  end
195
216
  end
data/lib/discorb/http.rb CHANGED
@@ -10,7 +10,12 @@ module Discorb
10
10
  class HTTP
11
11
  @nil_body = nil
12
12
 
13
+ #
14
+ # Initializes the http client.
13
15
  # @private
16
+ #
17
+ # @param [Discorb::Client] client The client.
18
+ #
14
19
  def initialize(client)
15
20
  @client = client
16
21
  @ratelimit_handler = RatelimitHandler.new(client)
@@ -32,13 +37,13 @@ module Discorb
32
37
  # @raise [Discorb::HTTPError] The request was failed.
33
38
  #
34
39
  def request(path, body = "", headers: nil, audit_log_reason: nil, **kwargs)
35
- Async do |task|
40
+ Async do |_task|
36
41
  @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
42
+ resp = if %i[post patch put].include? path.method
43
+ http.send(path.method, get_path(path), get_body(body), get_headers(headers, body, audit_log_reason), **kwargs)
44
+ else
45
+ http.send(path.method, get_path(path), get_headers(headers, body, audit_log_reason), **kwargs)
46
+ end
42
47
  data = get_response_data(resp)
43
48
  @ratelimit_handler.save(path, resp)
44
49
  handle_response(resp, data, path, body, headers, audit_log_reason, kwargs)
@@ -61,8 +66,8 @@ module Discorb
61
66
  #
62
67
  # @raise [Discorb::HTTPError] The request was failed.
63
68
  #
64
- def multipart_request(path, body = "", files, headers: nil, audit_log_reason: nil, **kwargs)
65
- Async do |task|
69
+ def multipart_request(path, body, files, headers: nil, audit_log_reason: nil, **kwargs)
70
+ Async do |_task|
66
71
  @ratelimit_handler.wait(path)
67
72
  req = Net::HTTP.const_get(path.method.to_s.capitalize).new(get_path(path), get_headers(headers, body, audit_log_reason), **kwargs)
68
73
  data = [
@@ -70,12 +75,23 @@ module Discorb
70
75
  ]
71
76
  files&.each_with_index do |file, i|
72
77
  next if file.nil?
73
- data << ["files[#{i}]", file.io, { filename: file.filename, content_type: file.content_type }]
78
+ if file.created_by == :discord
79
+ request_io = StringIO.new(
80
+ cdn_http.get(URI.parse(file.url).path, {
81
+ "Content-Type" => nil,
82
+ "User-Agent" => Discorb::USER_AGENT,
83
+ }).body
84
+ )
85
+ data << ["files[#{i}]", request_io, { filename: file.filename, content_type: file.content_type }]
86
+ else
87
+ data << ["files[#{i}]", file.io, { filename: file.filename, content_type: file.content_type }]
88
+ end
74
89
  end
75
90
  req.set_form(data, "multipart/form-data")
76
91
  session = Net::HTTP.new("discord.com", 443)
77
92
  session.use_ssl = true
78
93
  resp = session.request(req)
94
+ files&.then { _1.filter(&:will_close).each { |f| f.io.close } }
79
95
  data = get_response_data(resp)
80
96
  @ratelimit_handler.save(path, resp)
81
97
  handle_response(resp, data, path, body, headers, audit_log_reason, kwargs)
@@ -112,7 +128,7 @@ module Discorb
112
128
  { "User-Agent" => USER_AGENT, "authorization" => "Bot #{@client.token}" }
113
129
  else
114
130
  { "User-Agent" => USER_AGENT, "authorization" => "Bot #{@client.token}",
115
- "content-type" => "application/json" }
131
+ "content-type" => "application/json", }
116
132
  end
117
133
  ret.merge!(headers) if !headers.nil? && headers.length.positive?
118
134
  ret["X-Audit-Log-Reason"] = audit_log_reason unless audit_log_reason.nil?
@@ -143,15 +159,13 @@ module Discorb
143
159
  begin
144
160
  data = JSON.parse(resp.body, symbolize_names: true)
145
161
  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)
162
+ data = if resp.body.nil? || resp.body.empty?
163
+ nil
164
+ else
165
+ resp.body
166
+ end
154
167
  end
168
+ raise CloudFlareBanError.new(resp, @client) if resp["Via"].nil? && resp.code == "429" && data.is_a?(String)
155
169
  data
156
170
  end
157
171
 
@@ -161,6 +175,12 @@ module Discorb
161
175
  https
162
176
  end
163
177
 
178
+ def cdn_http
179
+ https = Net::HTTP.new("cdn.discordapp.com", 443)
180
+ https.use_ssl = true
181
+ https
182
+ end
183
+
164
184
  def recr_utf8(data)
165
185
  case data
166
186
  when Hash
@@ -38,13 +38,22 @@ module Discorb
38
38
  # @macro client_cache
39
39
  # @return [Discorb::Guild] The guild this integration is in.
40
40
 
41
- @expire_behavior = {
41
+ # @private
42
+ # @return [{Integer => String}] The map of the expire behavior.
43
+ EXPIRE_BEHAVIOR = {
42
44
  0 => :remove_role,
43
45
  1 => :kick,
44
- }
46
+ }.freeze
45
47
 
48
+ #
49
+ # Initialize a new integration.
46
50
  # @private
47
- def initialize(client, data, guild_id, no_cache: false)
51
+ #
52
+ # @param [Discorb::Client] client The client.
53
+ # @param [Hash] data The data of the welcome screen.
54
+ # @param [Discorb::Guild] guild The guild this integration is in.
55
+ #
56
+ def initialize(client, data, guild_id)
48
57
  @client = client
49
58
  @data = data
50
59
  @guild_id = guild_id
@@ -80,7 +89,7 @@ module Discorb
80
89
  @syncing = data[:syncing]
81
90
  @role_id = Snowflake.new(data[:role_id])
82
91
  @enable_emoticons = data[:enable_emoticons]
83
- @expire_behavior = self.class.expire_behavior[data[:expire_behavior]]
92
+ @expire_behavior = EXPIRE_BEHAVIOR[data[:expire_behavior]]
84
93
  @expire_grace_period = data[:expire_grace_period]
85
94
  @user = @client.users[data[:user][:id]] or Discorb::User.new(@client, data[:user])
86
95
  @account = Account.new(data[:account])
@@ -89,11 +98,6 @@ module Discorb
89
98
  @application = data[:application] and Application.new(@client, data[:application])
90
99
  end
91
100
 
92
- class << self
93
- # @private
94
- attr_reader :expire_behavior
95
- end
96
-
97
101
  #
98
102
  # Represents an account for an integration.
99
103
  #
@@ -103,7 +107,12 @@ module Discorb
103
107
  # @return [String] The name of the account.
104
108
  attr_reader :name
105
109
 
110
+ #
111
+ # Initialize a new account.
106
112
  # @private
113
+ #
114
+ # @param [Hash] data The data from Discord.
115
+ #
107
116
  def initialize(data)
108
117
  @id = data[:id]
109
118
  @name = data[:name]
@@ -129,7 +138,13 @@ module Discorb
129
138
  # @return [nil] If the application has no bot user.
130
139
  attr_reader :bot
131
140
 
141
+ #
142
+ # Initialize a new application.
132
143
  # @private
144
+ #
145
+ # @param [Discorb::Client] client The client.
146
+ # @param [Hash] data The data from Discord.
147
+ #
133
148
  def initialize(client, data)
134
149
  @id = Snowflake.new(data[:id])
135
150
  @name = data[:name]
@@ -3,7 +3,9 @@
3
3
  module Discorb
4
4
  # Represents intents.
5
5
  class Intents
6
- @intent_bits = {
6
+ # @private
7
+ # @return [{Symbol => Integer}] The mapping of intent names to bit values.
8
+ INTENT_BITS = {
7
9
  guilds: 1 << 0,
8
10
  members: 1 << 1,
9
11
  bans: 1 << 2,
@@ -19,6 +21,7 @@ module Discorb
19
21
  dm_messages: 1 << 12,
20
22
  dm_reactions: 1 << 13,
21
23
  dm_typing: 1 << 14,
24
+ message_content: 1 << 15,
22
25
  scheduled_events: 1 << 16,
23
26
  }.freeze
24
27
 
@@ -39,9 +42,13 @@ module Discorb
39
42
  # @param dm_messages [Boolean] Whether dm messages related events are enabled.
40
43
  # @param dm_reactions [Boolean] Whether dm reactions related events are enabled.
41
44
  # @param dm_typing [Boolean] Whether dm typing related events are enabled.
45
+ # @param message_content [Boolean] Whether message content will be sent with events.
42
46
  # @param scheduled_events [Boolean] Whether events related scheduled events are enabled.
43
47
  #
44
48
  # @note You must enable privileged intents to use `members` and/or `presences` intents.
49
+ # @note Message Content Intent is not required to use `message_content` intents for now,
50
+ # this will be required in April 30, 2022. [Learn More](https://support-dev.discord.com/hc/en-us/articles/4404772028055).
51
+ # You should specify `message_content` intent for preventing unexpected changes in the future.
45
52
  #
46
53
  def initialize(guilds: true,
47
54
  members: false,
@@ -58,6 +65,7 @@ module Discorb
58
65
  dm_messages: true,
59
66
  dm_reactions: true,
60
67
  dm_typing: true,
68
+ message_content: nil,
61
69
  scheduled_events: true)
62
70
  @raw_value = {
63
71
  guilds: guilds,
@@ -75,6 +83,7 @@ module Discorb
75
83
  dm_messages: dm_messages,
76
84
  dm_reactions: dm_reactions,
77
85
  dm_typing: dm_typing,
86
+ message_content: message_content,
78
87
  scheduled_events: scheduled_events,
79
88
  }
80
89
  end
@@ -86,7 +95,7 @@ module Discorb
86
95
  if @raw_value.key?(name)
87
96
  @raw_value[name]
88
97
  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)
98
+ raise ArgumentError, "true/false expected" unless args.is_a?(TrueClass) || args.is_a?(FalseClass)
90
99
 
91
100
  @raw_value[name[0..-2].to_sym] = args
92
101
  else
@@ -94,7 +103,7 @@ module Discorb
94
103
  end
95
104
  end
96
105
 
97
- def respond_to_missing?(sym, include_private)
106
+ def respond_to_missing?(name, include_private)
98
107
  @raw_value.key?(name) ? true : super
99
108
  end
100
109
 
@@ -102,7 +111,7 @@ module Discorb
102
111
  # @return [Integer] The value of the intent.
103
112
  def value
104
113
  res = 0
105
- self.class.intent_bits.each do |intent, bit|
114
+ INTENT_BITS.each do |intent, bit|
106
115
  res += bit if @raw_value[intent]
107
116
  end
108
117
  res
@@ -121,7 +130,7 @@ module Discorb
121
130
  # @param value [Integer] The value of the intent.
122
131
  def from_value(value)
123
132
  raw_value = {}
124
- @intent_bits.each do |intent, bit|
133
+ INTENT_BITS.each do |intent, bit|
125
134
  raw_value[intent] = value & bit != 0
126
135
  end
127
136
  new(**raw_value)
@@ -129,21 +138,17 @@ module Discorb
129
138
 
130
139
  # Create new intent object with default values.
131
140
  # This will return intents without members and presence.
132
- def default
133
- from_value(@intent_bits.values.reduce(:+) - @intent_bits[:members] - @intent_bits[:presences])
134
- end
141
+ alias default new
135
142
 
136
143
  # Create new intent object with all intents.
137
144
  def all
138
- from_value(@intent_bits.values.reduce(:+))
145
+ from_value(INTENT_BITS.values.reduce(:+))
139
146
  end
140
147
 
141
148
  # Create new intent object with no intents.
142
149
  def none
143
150
  from_value(0)
144
151
  end
145
-
146
- attr_reader :intent_bits
147
152
  end
148
153
  end
149
154
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Discorb
2
3
  #
3
4
  # Represents auto complete interaction.
@@ -6,7 +7,8 @@ module Discorb
6
7
  @interaction_type = 4
7
8
  @interaction_name = :auto_complete
8
9
 
9
- # @private
10
+ private
11
+
10
12
  def _set_data(data)
11
13
  super
12
14
  Sync do
@@ -17,15 +19,14 @@ module Discorb
17
19
  next
18
20
  end
19
21
 
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)
22
+ option_map = command.options.to_h { |k, v| [k.to_s, v[:default]] }
23
+ Discorb::CommandInteraction::SlashCommand.modify_option_map(option_map, options, guild, {}, {})
22
24
  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] })
25
+ 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
26
  send_complete_result(val)
25
27
  end
26
28
  end
27
29
 
28
- # @private
29
30
  def send_complete_result(val)
30
31
  @client.http.request(Route.new("/interactions/#{@id}/#{@token}/callback", "//interactions/:interaction_id/:token/callback", :post), {
31
32
  type: 8,
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Discorb
2
3
  #
3
4
  # Represents a command interaction.
@@ -5,13 +6,15 @@ 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.
12
14
  #
13
15
  class SlashCommand < CommandInteraction
14
16
  @command_type = 1
17
+ @event_name = :slash_command
15
18
 
16
19
  private
17
20
 
@@ -25,14 +28,19 @@ module Discorb
25
28
  return
26
29
  end
27
30
 
28
- option_map = command.options.map { |k, v| [k.to_s, v[:default]] }.to_h
29
- SlashCommand.modify_option_map(option_map, options, guild)
31
+ option_map = command.options.to_h { |k, v| [k.to_s, v[:default]] }
32
+ SlashCommand.modify_option_map(option_map, options, guild, @members, @attachments)
30
33
 
31
- command.block.call(self, *command.options.map { |k, v| option_map[k.to_s] })
34
+ command.block.call(self, *command.options.map { |k, _v| option_map[k.to_s] })
32
35
  end
33
36
 
34
37
  class << self
38
+ #
39
+ # Get command data from the given data.
35
40
  # @private
41
+ #
42
+ # @param [Hash] data The data of the command.
43
+ #
36
44
  def get_command_data(data)
37
45
  name = data[:name]
38
46
  options = nil
@@ -55,24 +63,34 @@ module Discorb
55
63
  options = data[:options]
56
64
  end
57
65
 
58
- return name, options
66
+ [name, options]
59
67
  end
60
68
 
69
+ #
70
+ # Modify the option map with the given options.
61
71
  # @private
62
- def modify_option_map(option_map, options, guild)
72
+ #
73
+ # @param [Hash] option_map The option map to modify.
74
+ # @param [Array<Hash>] options The options for modifying.
75
+ # @param [Discorb::Guild] guild The guild where the command is executed.
76
+ # @param [{Discorb::Snowflake => Discorb::Member}] members The cached members of the guild.
77
+ # @param [{Integer => Discorb::Attachment}] attachments The cached attachments of the message.
78
+ def modify_option_map(option_map, options, guild, members, attachments)
63
79
  options ||= []
64
- options.each_with_index do |option|
80
+ options.each do |option|
65
81
  val = case option[:type]
66
82
  when 3, 4, 5, 10
67
83
  option[:value]
68
84
  when 6
69
- guild.members[option[:value]] || guild.fetch_member(option[:value]).wait
85
+ members[option[:value]] || guild.members[option[:value]] || guild.fetch_member(option[:value]).wait
70
86
  when 7
71
87
  guild.channels[option[:value]] || guild.fetch_channels.wait.find { |channel| channel.id == option[:value] }
72
88
  when 8
73
89
  guild.roles[option[:value]] || guild.fetch_roles.wait.find { |role| role.id == option[:value] }
74
90
  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] }
91
+ 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] }
92
+ when 11
93
+ attachments[option[:value]]
76
94
  end
77
95
  option_map[option[:name]] = val
78
96
  end
@@ -85,6 +103,7 @@ module Discorb
85
103
  #
86
104
  class UserMenuCommand < CommandInteraction
87
105
  @command_type = 2
106
+ @event_name = :user_command
88
107
 
89
108
  # @return [Discorb::Member, Discorb::User] The target user.
90
109
  attr_reader :target
@@ -92,6 +111,7 @@ module Discorb
92
111
  private
93
112
 
94
113
  def _set_data(data)
114
+ super
95
115
  @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
116
  @client.commands.find { |c| c.name == data[:name] && c.type_raw == 2 }.block.call(self, @target)
97
117
  end
@@ -102,6 +122,7 @@ module Discorb
102
122
  #
103
123
  class MessageMenuCommand < CommandInteraction
104
124
  @command_type = 3
125
+ @event_name = :message_command
105
126
 
106
127
  # @return [Discorb::Message] The target message.
107
128
  attr_reader :target
@@ -109,7 +130,8 @@ module Discorb
109
130
  private
110
131
 
111
132
  def _set_data(data)
112
- @target = Message.new(@client, data[:resolved][:messages][data[:target_id].to_sym].merge(guild_id: @guild_id.to_s))
133
+ super
134
+ @target = @messages[data[:target_id]]
113
135
  @client.commands.find { |c| c.name == data[:name] && c.type_raw == 3 }.block.call(self, @target)
114
136
  end
115
137
  end
@@ -117,23 +139,55 @@ module Discorb
117
139
  private
118
140
 
119
141
  def _set_data(data)
142
+ super
120
143
  @name = data[:name]
144
+ @messages, @attachments, @members = {}, {}, {}
145
+
146
+ if data[:resolved]
147
+ data[:resolved][:users]&.each do |id, user|
148
+ @client.users[id] = Discorb::User.new(@client, user)
149
+ end
150
+ data[:resolved][:members]&.each do |id, member|
151
+ @members[id] = Discorb::Member.new(
152
+ @client, @guild_id, data[:resolved][:users][id], member
153
+ )
154
+ end
155
+ data[:resolved][:messages]&.to_h do |id, _message|
156
+ @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)
157
+ end
158
+ data[:resolved][:attachments]&.to_h do |id, attachment|
159
+ @attachments[id.to_s] = Attachment.new(attachment)
160
+ end
161
+ end
121
162
  end
122
163
 
123
164
  class << self
124
165
  # @private
125
- attr_reader :command_type
166
+ attr_reader :command_type, :event_name
126
167
 
168
+ #
169
+ # Creates a new CommandInteraction instance for the given data.
127
170
  # @private
171
+ #
172
+ # @param [Discorb::Client] client The client.
173
+ # @param [Hash] data The data for the command.
174
+ #
128
175
  def make_interaction(client, data)
129
176
  nested_classes.each do |klass|
130
- return klass.new(client, data) if !klass.command_type.nil? && klass.command_type == data[:data][:type]
177
+ if !klass.command_type.nil? && klass.command_type == data[:data][:type]
178
+ interaction = klass.new(client, data)
179
+ client.dispatch(klass.event_name, interaction)
180
+ return interaction
181
+ end
131
182
  end
132
183
  client.log.warn("Unknown command type #{data[:type]}, initialized CommandInteraction")
133
184
  CommandInteraction.new(client, data)
134
185
  end
135
186
 
187
+ #
188
+ # Returns the classes under this class.
136
189
  # @private
190
+ #
137
191
  def nested_classes
138
192
  constants.select { |c| const_get(c).is_a? Class }.map { |c| const_get(c) }
139
193
  end
@@ -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.
@@ -15,17 +18,27 @@ module Discorb
15
18
  @interaction_type = 3
16
19
  @interaction_name = :message_component
17
20
 
21
+ #
22
+ # Initialize a new message component interaction.
18
23
  # @private
24
+ #
25
+ # @param [Discorb::Client] client The client.
26
+ # @param [Hash] data The data.
27
+ #
19
28
  def initialize(client, data)
20
29
  super
21
- @message = Message.new(@client, data[:message].merge({ member: data[:member] }))
30
+ @message = Message.new(@client, data[:message].merge({ member: data[:member], guild_id: data[:guild_id] }))
22
31
  end
23
32
 
24
33
  class << self
25
34
  # @private
35
+ # @return [Integer] The component type.
26
36
  attr_reader :component_type
27
37
 
38
+ #
39
+ # Create a MessageComponentInteraction instance for the given data.
28
40
  # @private
41
+ #
29
42
  def make_interaction(client, data)
30
43
  nested_classes.each do |klass|
31
44
  return klass.new(client, data) if !klass.component_type.nil? && klass.component_type == data[:data][:component_type]
@@ -34,7 +47,10 @@ module Discorb
34
47
  MessageComponentInteraction.new(client, data)
35
48
  end
36
49
 
50
+ #
51
+ # Returns the classes under this class.
37
52
  # @private
53
+ #
38
54
  def nested_classes
39
55
  constants.select { |c| const_get(c).is_a? Class }.map { |c| const_get(c) }
40
56
  end
@@ -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