discorb 0.11.1 → 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.
@@ -0,0 +1,142 @@
1
+ module Discorb
2
+ #
3
+ # Represents a command interaction.
4
+ #
5
+ class CommandInteraction < Interaction
6
+ @interaction_type = 2
7
+ @interaction_name = :application_command
8
+ include Interaction::SourceResponse
9
+
10
+ #
11
+ # Represents a slash command interaction.
12
+ #
13
+ class SlashCommand < CommandInteraction
14
+ @command_type = 1
15
+
16
+ private
17
+
18
+ def _set_data(data)
19
+ super
20
+
21
+ name, options = SlashCommand.get_command_data(data)
22
+
23
+ unless (command = @client.bottom_commands.find { |c| c.to_s == name && c.type_raw == 1 })
24
+ @client.log.warn "Unknown command name #{name}, ignoring"
25
+ return
26
+ end
27
+
28
+ option_map = command.options.map { |k, v| [k.to_s, v[:default]] }.to_h
29
+ SlashCommand.modify_option_map(option_map, options)
30
+
31
+ command.block.call(self, *command.options.map { |k, v| option_map[k.to_s] })
32
+ end
33
+
34
+ class << self
35
+ # @private
36
+ def get_command_data(data)
37
+ name = data[:name]
38
+ options = nil
39
+ return name, options unless (option = data[:options]&.first)
40
+
41
+ case option[:type]
42
+ when 1
43
+ name += " #{option[:name]}"
44
+ options = option[:options]
45
+ when 2
46
+ name += " #{option[:name]}"
47
+ unless option[:options]&.first&.[](:type) == 1
48
+ options = option[:options]
49
+ return name, options
50
+ end
51
+ option_sub = option[:options]&.first
52
+ name += " #{option_sub[:name]}"
53
+ options = option_sub[:options]
54
+ else
55
+ options = data[:options]
56
+ end
57
+
58
+ return name, options
59
+ end
60
+
61
+ # @private
62
+ def modify_option_map(option_map, options)
63
+ options ||= []
64
+ options.each_with_index do |option|
65
+ val = case option[:type]
66
+ when 3, 4, 5, 10
67
+ option[:value]
68
+ when 6
69
+ guild.members[option[:value]] || guild.fetch_member(option[:value]).wait
70
+ when 7
71
+ guild.channels[option[:value]] || guild.fetch_channels.wait.find { |channel| channel.id == option[:value] }
72
+ when 8
73
+ guild.roles[option[:value]] || guild.fetch_roles.wait.find { |role| role.id == option[:value] }
74
+ 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] }
76
+ end
77
+ option_map[option[:name]] = val
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ #
84
+ # Represents a user context menu interaction.
85
+ #
86
+ class UserMenuCommand < CommandInteraction
87
+ @command_type = 2
88
+
89
+ # @return [Discorb::Member, Discorb::User] The target user.
90
+ attr_reader :target
91
+
92
+ private
93
+
94
+ def _set_data(data)
95
+ @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
+ @client.commands.find { |c| c.name == data[:name] && c.type_raw == 2 }.block.call(self, @target)
97
+ end
98
+ end
99
+
100
+ #
101
+ # Represents a message context menu interaction.
102
+ #
103
+ class MessageMenuCommand < CommandInteraction
104
+ @command_type = 3
105
+
106
+ # @return [Discorb::Message] The target message.
107
+ attr_reader :target
108
+
109
+ private
110
+
111
+ def _set_data(data)
112
+ @target = Message.new(@client, data[:resolved][:messages][data[:target_id].to_sym].merge(guild_id: @guild_id.to_s))
113
+ @client.commands.find { |c| c.name == data[:name] && c.type_raw == 3 }.block.call(self, @target)
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ def _set_data(data)
120
+ @name = data[:name]
121
+ end
122
+
123
+ class << self
124
+ # @private
125
+ attr_reader :command_type
126
+
127
+ # @private
128
+ def make_interaction(client, data)
129
+ nested_classes.each do |klass|
130
+ return klass.new(client, data) if !klass.command_type.nil? && klass.command_type == data[:data][:type]
131
+ end
132
+ client.log.warn("Unknown command type #{data[:type]}, initialized CommandInteraction")
133
+ CommandInteraction.new(client, data)
134
+ end
135
+
136
+ # @private
137
+ def nested_classes
138
+ constants.select { |c| const_get(c).is_a? Class }.map { |c| const_get(c) }
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,85 @@
1
+ module Discorb
2
+
3
+ #
4
+ # Represents a message component interaction.
5
+ # @abstract
6
+ #
7
+ class MessageComponentInteraction < Interaction
8
+ include Interaction::SourceResponse
9
+ include Interaction::UpdateResponse
10
+ # @return [String] The content of the response.
11
+ attr_reader :custom_id
12
+ # @return [Discorb::Message] The target message.
13
+ attr_reader :message
14
+
15
+ @interaction_type = 3
16
+ @interaction_name = :message_component
17
+
18
+ # @private
19
+ def initialize(client, data)
20
+ super
21
+ @message = Message.new(@client, data[:message].merge({ member: data[:member] }))
22
+ end
23
+
24
+ class << self
25
+ # @private
26
+ attr_reader :component_type
27
+
28
+ # @private
29
+ def make_interaction(client, data)
30
+ nested_classes.each do |klass|
31
+ return klass.new(client, data) if !klass.component_type.nil? && klass.component_type == data[:data][:component_type]
32
+ end
33
+ client.log.warn("Unknown component type #{data[:component_type]}, initialized Interaction")
34
+ MessageComponentInteraction.new(client, data)
35
+ end
36
+
37
+ # @private
38
+ def nested_classes
39
+ constants.select { |c| const_get(c).is_a? Class }.map { |c| const_get(c) }
40
+ end
41
+ end
42
+
43
+ #
44
+ # Represents a button interaction.
45
+ #
46
+ class Button < MessageComponentInteraction
47
+ @component_type = 2
48
+ @event_name = :button_click
49
+ # @return [String] The custom id of the button.
50
+ attr_reader :custom_id
51
+
52
+ private
53
+
54
+ def _set_data(data)
55
+ @custom_id = data[:custom_id]
56
+ end
57
+ end
58
+
59
+ #
60
+ # Represents a select menu interaction.
61
+ #
62
+ class SelectMenu < MessageComponentInteraction
63
+ @component_type = 3
64
+ @event_name = :select_menu_select
65
+ # @return [String] The custom id of the select menu.
66
+ attr_reader :custom_id
67
+ # @return [Array<String>] The selected options.
68
+ attr_reader :values
69
+
70
+ # @!attribute [r] value
71
+ # @return [String] The first selected value.
72
+
73
+ def value
74
+ @values[0]
75
+ end
76
+
77
+ private
78
+
79
+ def _set_data(data)
80
+ @custom_id = data[:custom_id]
81
+ @values = data[:values]
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,188 @@
1
+ module Discorb
2
+ class Interaction
3
+ #
4
+ # A module for response with source.
5
+ #
6
+ module SourceResponse
7
+ #
8
+ # Response with `DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE`(`5`).
9
+ #
10
+ # @macro async
11
+ # @macro http
12
+ #
13
+ # @param [Boolean] ephemeral Whether to make the response ephemeral.
14
+ #
15
+ def defer_source(ephemeral: false)
16
+ Async do
17
+ @client.http.post("/interactions/#{@id}/#{@token}/callback", {
18
+ type: 5,
19
+ data: {
20
+ flags: (ephemeral ? 1 << 6 : 0),
21
+ },
22
+ }).wait
23
+ @defered = true
24
+ end
25
+ end
26
+
27
+ #
28
+ # Response with `CHANNEL_MESSAGE_WITH_SOURCE`(`4`).
29
+ #
30
+ # @macro async
31
+ # @macro http
32
+ #
33
+ # @param [String] content The content of the response.
34
+ # @param [Boolean] tts Whether to send the message as text-to-speech.
35
+ # @param [Discorb::Embed] embed The embed to send.
36
+ # @param [Array<Discorb::Embed>] embeds The embeds to send. (max: 10)
37
+ # @param [Discorb::AllowedMentions] allowed_mentions The allowed mentions to send.
38
+ # @param [Array<Discorb::Component>, Array<Array<Discorb::Component>>] components The components to send.
39
+ # @param [Boolean] ephemeral Whether to make the response ephemeral.
40
+ #
41
+ # @return [Discorb::Interaction::SourceResponse::CallbackMessage, Discorb::Webhook::Message] The callback message.
42
+ #
43
+ def post(content = nil, tts: false, embed: nil, embeds: nil, allowed_mentions: nil, components: nil, ephemeral: false)
44
+ Async do
45
+ payload = {}
46
+ payload[:content] = content if content
47
+ payload[:tts] = tts
48
+ payload[:embeds] = (embeds || [embed])&.map { |e| e&.to_hash }.filter { _1 }
49
+ payload[:allowed_mentions] = allowed_mentions&.to_hash(@client.allowed_mentions) || @client.allowed_mentions.to_hash
50
+ payload[:components] = Component.to_payload(components) if components
51
+ payload[:flags] = (ephemeral ? 1 << 6 : 0)
52
+
53
+ ret = if @responded
54
+ _resp, data = @client.http.post("/webhooks/#{@application_id}/#{@token}", payload).wait
55
+ webhook = Webhook::URLWebhook.new("/webhooks/#{@application_id}/#{@token}")
56
+ Webhook::Message.new(webhook, data, @client)
57
+ elsif @defered
58
+ @client.http.patch("/webhooks/#{@application_id}/#{@token}/messages/@original", payload).wait
59
+ CallbackMessage.new(@client, payload, @application_id, @token)
60
+ else
61
+ @client.http.post("/interactions/#{@id}/#{@token}/callback", { type: 4, data: payload }).wait
62
+ CallbackMessage.new(@client, payload, @application_id, @token)
63
+ end
64
+ @responded = true
65
+ ret
66
+ end
67
+ end
68
+
69
+ class CallbackMessage
70
+ # @private
71
+ def initialize(client, data, application_id, token)
72
+ @client = client
73
+ @data = data
74
+ @application_id = application_id
75
+ @token = token
76
+ end
77
+
78
+ #
79
+ # Edits the callback message.
80
+ # @macro async
81
+ # @macro http
82
+ # @macro edit
83
+ #
84
+ # @param [String] content The new content of the message.
85
+ # @param [Discorb::Embed] embed The new embed of the message.
86
+ # @param [Array<Discorb::Embed>] embeds The new embeds of the message.
87
+ # @param [Array<Discorb::Attachment>] attachments The attachments to remain.
88
+ # @param [Discorb::File] file The file to send.
89
+ # @param [Array<Discorb::File>] files The files to send.
90
+ #
91
+ def edit(
92
+ content = :unset,
93
+ embed: :unset, embeds: :unset,
94
+ file: :unset, files: :unset,
95
+ attachments: :unset
96
+ )
97
+ Async do
98
+ payload = {}
99
+ payload[:content] = content if content != :unset
100
+ payload[:embeds] = embed ? [embed.to_hash] : [] if embed != :unset
101
+ payload[:embeds] = embeds.map(&:to_hash) if embeds != :unset
102
+ payload[:attachments] = attachments.map(&:to_hash) if attachments != :unset
103
+ files = [file] if file != :unset
104
+ if files == :unset
105
+ headers = {
106
+ "Content-Type" => "application/json",
107
+ }
108
+ else
109
+ headers, payload = HTTP.multipart(payload, files)
110
+ end
111
+ @client.http.patch("/webhooks/#{@application_id}/#{@token}/messages/@original", payload, headers: headers).wait
112
+ end
113
+ end
114
+
115
+ alias modify edit
116
+
117
+ #
118
+ # Deletes the callback message.
119
+ # @note This will fail if the message is ephemeral.
120
+ #
121
+ def delete!
122
+ Async do
123
+ @client.http.delete("/webhooks/#{@application_id}/#{@token}/messages/@original").wait
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ #
130
+ # A module for response with update.
131
+ #
132
+ module UpdateResponse
133
+ #
134
+ # Response with `DEFERRED_UPDATE_MESSAGE`(`6`).
135
+ #
136
+ # @param [Boolean] ephemeral Whether to make the response ephemeral.
137
+ #
138
+ def defer_update(ephemeral: false)
139
+ Async do
140
+ @client.http.post("/interactions/#{@id}/#{@token}/callback", {
141
+ type: 6,
142
+ data: {
143
+ flags: (ephemeral ? 1 << 6 : 0),
144
+ },
145
+ }).wait
146
+ end
147
+ end
148
+
149
+ #
150
+ # Response with `UPDATE_MESSAGE`(`7`).
151
+ #
152
+ # @macro async
153
+ # @macro http
154
+ #
155
+ # @param [String] content The content of the response.
156
+ # @param [Boolean] tts Whether to send the message as text-to-speech.
157
+ # @param [Discorb::Embed] embed The embed to send.
158
+ # @param [Array<Discorb::Embed>] embeds The embeds to send. (max: 10)
159
+ # @param [Discorb::AllowedMentions] allowed_mentions The allowed mentions to send.
160
+ # @param [Array<Discorb::Component>, Array<Array<Discorb::Component>>] components The components to send.
161
+ # @param [Boolean] ephemeral Whether to make the response ephemeral.
162
+ #
163
+ def edit(content, tts: false, embed: nil, embeds: nil, allowed_mentions: nil, components: nil, ephemeral: false)
164
+ Async do
165
+ payload = {}
166
+ payload[:content] = content if content
167
+ payload[:tts] = tts
168
+ tmp_embed = if embed
169
+ [embed]
170
+ elsif embeds
171
+ embeds
172
+ end
173
+ payload[:embeds] = tmp_embed.map(&:to_hash) if tmp_embed
174
+ payload[:allowed_mentions] = allowed_mentions ? allowed_mentions.to_hash(@client.allowed_mentions) : @client.allowed_mentions.to_hash
175
+ payload[:components] = Component.to_payload(components) if components
176
+ payload[:flags] = (ephemeral ? 1 << 6 : 0)
177
+ @client.http.post("/interactions/#{@id}/#{@token}/callback", { type: 7, data: payload }).wait
178
+ end
179
+ end
180
+ end
181
+
182
+ private
183
+
184
+ def _set_data(*)
185
+ nil
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,94 @@
1
+ module Discorb
2
+ #
3
+ # Represents a user interaction with the bot.
4
+ #
5
+ class Interaction < DiscordModel
6
+ # @return [Discorb::Snowflake] The ID of the interaction.
7
+ attr_reader :id
8
+ # @return [Discorb::Snowflake] The ID of the application that created the interaction.
9
+ attr_reader :application_id
10
+ # @return [Symbol] The type of interaction.
11
+ attr_reader :type
12
+ # @return [Discorb::Member] The member that created the interaction.
13
+ attr_reader :member
14
+ # @return [Discorb::User] The user that created the interaction.
15
+ attr_reader :user
16
+ # @return [Integer] The type of interaction.
17
+ # @note This is always `1` for now.
18
+ attr_reader :version
19
+ # @return [String] The token for the interaction.
20
+ attr_reader :token
21
+
22
+ # @!attribute [r] guild
23
+ # @macro client_cache
24
+ # @return [Discorb::Guild] The guild the interaction took place in.
25
+ # @!attribute [r] channel
26
+ # @macro client_cache
27
+ # @return [Discorb::Channel] The channel the interaction took place in.
28
+ # @!attribute [r] target
29
+ # @return [Discorb::User, Discorb::Member] The user or member the interaction took place with.
30
+
31
+ @interaction_type = nil
32
+ @interaction_name = nil
33
+
34
+ # @private
35
+ def initialize(client, data)
36
+ @client = client
37
+ @id = Snowflake.new(data[:id])
38
+ @application_id = Snowflake.new(data[:application_id])
39
+ @type = self.class.interaction_name
40
+ @type_id = self.class.interaction_type
41
+ @guild_id = data[:guild_id] && Snowflake.new(data[:guild_id])
42
+ @channel_id = data[:channel_id] && Snowflake.new(data[:channel_id])
43
+ @member = guild.members[data[:member][:id]] || Member.new(@client, @guild_id, data[:member][:user], data[:member]) if data[:member]
44
+ @user = @client.users[data[:user][:id]] || User.new(@client, data[:user]) if data[:user]
45
+ @token = data[:token]
46
+ @version = data[:version]
47
+ @defered = false
48
+ @responded = false
49
+ _set_data(data[:data])
50
+ end
51
+
52
+ def guild
53
+ @client.guilds[@guild_id]
54
+ end
55
+
56
+ def channel
57
+ @client.channels[@channel_id]
58
+ end
59
+
60
+ def target
61
+ @member || @user
62
+ end
63
+
64
+ alias fired_by target
65
+ alias from target
66
+
67
+ def inspect
68
+ "#<#{self.class} id=#{@id}>"
69
+ end
70
+
71
+ class << self
72
+ # @private
73
+ attr_reader :interaction_type, :interaction_name, :event_name
74
+
75
+ # @private
76
+ def make_interaction(client, data)
77
+ interaction = nil
78
+ descendants.each do |klass|
79
+ interaction = klass.make_interaction(client, data) if !klass.interaction_type.nil? && klass.interaction_type == data[:type]
80
+ end
81
+ if interaction.nil?
82
+ client.log.warn("Unknown interaction type #{data[:type]}, initialized Interaction")
83
+ interaction = Interaction.new(client, data)
84
+ end
85
+ interaction
86
+ end
87
+
88
+ # @private
89
+ def descendants
90
+ ObjectSpace.each_object(Class).select { |klass| klass < self }
91
+ end
92
+ end
93
+ end
94
+ end