discordrb 3.4.0 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +44 -18
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -1
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -1
- data/.github/workflows/codeql.yml +65 -0
- data/.markdownlint.json +4 -0
- data/.rubocop.yml +8 -2
- data/CHANGELOG.md +419 -222
- data/LICENSE.txt +1 -1
- data/README.md +37 -25
- data/discordrb-webhooks.gemspec +4 -1
- data/discordrb.gemspec +9 -6
- data/lib/discordrb/api/application.rb +202 -0
- data/lib/discordrb/api/channel.rb +182 -11
- data/lib/discordrb/api/interaction.rb +54 -0
- data/lib/discordrb/api/invite.rb +2 -2
- data/lib/discordrb/api/server.rb +42 -19
- data/lib/discordrb/api/user.rb +9 -3
- data/lib/discordrb/api/webhook.rb +57 -0
- data/lib/discordrb/api.rb +19 -5
- data/lib/discordrb/bot.rb +328 -33
- data/lib/discordrb/cache.rb +27 -22
- data/lib/discordrb/commands/command_bot.rb +14 -7
- data/lib/discordrb/commands/container.rb +1 -1
- data/lib/discordrb/commands/parser.rb +2 -2
- data/lib/discordrb/commands/rate_limiter.rb +1 -1
- data/lib/discordrb/container.rb +132 -3
- data/lib/discordrb/data/activity.rb +8 -1
- data/lib/discordrb/data/attachment.rb +15 -0
- data/lib/discordrb/data/audit_logs.rb +3 -3
- data/lib/discordrb/data/channel.rb +167 -23
- data/lib/discordrb/data/component.rb +229 -0
- data/lib/discordrb/data/integration.rb +42 -3
- data/lib/discordrb/data/interaction.rb +800 -0
- data/lib/discordrb/data/invite.rb +2 -2
- data/lib/discordrb/data/member.rb +108 -33
- data/lib/discordrb/data/message.rb +100 -20
- data/lib/discordrb/data/overwrite.rb +13 -7
- data/lib/discordrb/data/role.rb +58 -1
- data/lib/discordrb/data/server.rb +82 -80
- data/lib/discordrb/data/user.rb +69 -9
- data/lib/discordrb/data/webhook.rb +97 -4
- data/lib/discordrb/data.rb +3 -0
- data/lib/discordrb/errors.rb +44 -3
- data/lib/discordrb/events/channels.rb +1 -1
- data/lib/discordrb/events/interactions.rb +482 -0
- data/lib/discordrb/events/message.rb +9 -6
- data/lib/discordrb/events/presence.rb +21 -14
- data/lib/discordrb/events/reactions.rb +0 -1
- data/lib/discordrb/events/threads.rb +96 -0
- data/lib/discordrb/gateway.rb +30 -17
- data/lib/discordrb/permissions.rb +59 -34
- data/lib/discordrb/version.rb +1 -1
- data/lib/discordrb/voice/encoder.rb +13 -4
- data/lib/discordrb/voice/network.rb +18 -7
- data/lib/discordrb/voice/sodium.rb +3 -1
- data/lib/discordrb/voice/voice_bot.rb +3 -3
- data/lib/discordrb/webhooks.rb +2 -0
- data/lib/discordrb.rb +37 -4
- metadata +53 -19
- data/.codeclimate.yml +0 -16
- data/.travis.yml +0 -32
- data/bin/travis_build_docs.sh +0 -17
data/lib/discordrb/errors.rb
CHANGED
@@ -20,6 +20,9 @@ module Discordrb
|
|
20
20
|
# Raised when the bot gets a HTTP 502 error, which is usually caused by Cloudflare.
|
21
21
|
class CloudflareError < RuntimeError; end
|
22
22
|
|
23
|
+
# Raised when using a webhook method without an associated token.
|
24
|
+
class UnauthorizedWebhook < RuntimeError; end
|
25
|
+
|
23
26
|
# Generic class for errors denoted by API error codes
|
24
27
|
class CodeError < RuntimeError
|
25
28
|
class << self
|
@@ -29,8 +32,11 @@ module Discordrb
|
|
29
32
|
|
30
33
|
# Create a new error with a particular message (the code should be defined by the class instance variable)
|
31
34
|
# @param message [String] the message to use
|
32
|
-
|
35
|
+
# @param errors [Hash] API errors
|
36
|
+
def initialize(message, errors = nil)
|
33
37
|
@message = message
|
38
|
+
|
39
|
+
@errors = errors ? flatten_errors(errors) : []
|
34
40
|
end
|
35
41
|
|
36
42
|
# @return [Integer] The error code represented by this error.
|
@@ -38,15 +44,50 @@ module Discordrb
|
|
38
44
|
self.class.code
|
39
45
|
end
|
40
46
|
|
47
|
+
# @return [String] A message including the message and flattened errors.
|
48
|
+
def full_message(*)
|
49
|
+
error_list = @errors.collect { |err| "\t- #{err}" }
|
50
|
+
|
51
|
+
"#{@message}\n#{error_list.join("\n")}"
|
52
|
+
end
|
53
|
+
|
41
54
|
# @return [String] This error's represented message
|
42
55
|
attr_reader :message
|
56
|
+
|
57
|
+
# @return [Hash] More precise errors
|
58
|
+
attr_reader :errors
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# @!visibility hidden
|
63
|
+
# Flattens errors into a more easily read format.
|
64
|
+
# @example Flattening errors of a bad field
|
65
|
+
# flatten_errors(data['errors'])
|
66
|
+
# # => ["embed.fields[0].name: This field is required", "embed.fields[0].value: This field is required"]
|
67
|
+
def flatten_errors(err, prev_key = nil)
|
68
|
+
err.collect do |key, sub_err|
|
69
|
+
if prev_key
|
70
|
+
key = /\A\d+\Z/.match?(key) ? "#{prev_key}[#{key}]" : "#{prev_key}.#{key}"
|
71
|
+
end
|
72
|
+
|
73
|
+
if (errs = sub_err['_errors'])
|
74
|
+
"#{key}: #{errs.map { |e| e['message'] }.join(' ')}"
|
75
|
+
elsif sub_err['message'] || sub_err['code']
|
76
|
+
"#{sub_err['code'] ? "#{sub_err['code']}: " : nil}#{err_msg}"
|
77
|
+
elsif sub_err.is_a? String
|
78
|
+
sub_err
|
79
|
+
else
|
80
|
+
flatten_errors(sub_err, key)
|
81
|
+
end
|
82
|
+
end.flatten
|
83
|
+
end
|
43
84
|
end
|
44
85
|
|
45
86
|
# Create a new code error class
|
46
87
|
# rubocop:disable Naming/MethodName
|
47
88
|
def self.Code(code)
|
48
89
|
classy = Class.new(CodeError)
|
49
|
-
classy.instance_variable_set(
|
90
|
+
classy.instance_variable_set(:@code, code)
|
50
91
|
|
51
92
|
@code_classes ||= {}
|
52
93
|
@code_classes[code] = classy
|
@@ -58,7 +99,7 @@ module Discordrb
|
|
58
99
|
# @param code [Integer] The code to check
|
59
100
|
# @return [Class] the error class for the given code
|
60
101
|
def self.error_class_for(code)
|
61
|
-
@code_classes[code]
|
102
|
+
@code_classes[code] || UnknownError
|
62
103
|
end
|
63
104
|
|
64
105
|
# Used when Discord doesn't provide a more specific code
|
@@ -0,0 +1,482 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'discordrb/events/generic'
|
4
|
+
require 'discordrb/data'
|
5
|
+
|
6
|
+
module Discordrb::Events
|
7
|
+
# Generic subclass for interaction events
|
8
|
+
class InteractionCreateEvent < Event
|
9
|
+
# @return [Interaction] The interaction for this event.
|
10
|
+
attr_reader :interaction
|
11
|
+
|
12
|
+
# @!attribute [r] type
|
13
|
+
# @return [Integer]
|
14
|
+
# @see Interaction#type
|
15
|
+
# @!attribute [r] server
|
16
|
+
# @return [Server, nil]
|
17
|
+
# @see Interaction#server
|
18
|
+
# @!attribute [r] server_id
|
19
|
+
# @return [Integer]
|
20
|
+
# @see Interaction#server_id
|
21
|
+
# @!attribute [r] channel
|
22
|
+
# @return [Channel]
|
23
|
+
# @see Interaction#channel
|
24
|
+
# @!attribute [r] channel_id
|
25
|
+
# @return [Integer]
|
26
|
+
# @see Interaction#channel_id
|
27
|
+
# @!attribute [r] user
|
28
|
+
# @return [User]
|
29
|
+
# @see Interaction#user
|
30
|
+
delegate :type, :server, :server_id, :channel, :channel_id, :user, to: :interaction
|
31
|
+
|
32
|
+
def initialize(data, bot)
|
33
|
+
@interaction = Discordrb::Interaction.new(data, bot)
|
34
|
+
@bot = bot
|
35
|
+
end
|
36
|
+
|
37
|
+
# (see Interaction#respond)
|
38
|
+
def respond(content: nil, tts: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: nil, wait: false, components: nil, &block)
|
39
|
+
@interaction.respond(
|
40
|
+
content: content, tts: tts, embeds: embeds, allowed_mentions: allowed_mentions,
|
41
|
+
flags: flags, ephemeral: ephemeral, wait: wait, components: components, &block
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
# (see Interaction#defer)
|
46
|
+
def defer(flags: 0, ephemeral: true)
|
47
|
+
@interaction.defer(flags: flags, ephemeral: ephemeral)
|
48
|
+
end
|
49
|
+
|
50
|
+
# (see Interaction#update_message)
|
51
|
+
def update_message(content: nil, tts: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: nil, wait: false, components: nil, &block)
|
52
|
+
@interaction.update_message(
|
53
|
+
content: content, tts: tts, embeds: embeds, allowed_mentions: allowed_mentions,
|
54
|
+
flags: flags, ephemeral: ephemeral, wait: wait, components: components, &block
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
# (see Interaction#show_modal)
|
59
|
+
def show_modal(title:, custom_id:, components: nil, &block)
|
60
|
+
@interaction.show_modal(title: title, custom_id: custom_id, components: components, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
# (see Interaction#edit_response)
|
64
|
+
def edit_response(content: nil, embeds: nil, allowed_mentions: nil, components: nil, &block)
|
65
|
+
@interaction.edit_response(content: content, embeds: embeds, allowed_mentions: allowed_mentions, components: components, &block)
|
66
|
+
end
|
67
|
+
|
68
|
+
# (see Interaction#delete_response)
|
69
|
+
def delete_response
|
70
|
+
@interaction.delete_response
|
71
|
+
end
|
72
|
+
|
73
|
+
# (see Interaction#send_message)
|
74
|
+
def send_message(content: nil, embeds: nil, tts: false, allowed_mentions: nil, flags: 0, ephemeral: nil, components: nil, &block)
|
75
|
+
@interaction.send_message(content: content, embeds: embeds, tts: tts, allowed_mentions: allowed_mentions, flags: flags, ephemeral: ephemeral, components: components, &block)
|
76
|
+
end
|
77
|
+
|
78
|
+
# (see Interaction#edit_message)
|
79
|
+
def edit_message(message, content: nil, embeds: nil, allowed_mentions: nil, &block)
|
80
|
+
@interaction.edit_message(message, content: content, embeds: embeds, allowed_mentions: allowed_mentions, &block)
|
81
|
+
end
|
82
|
+
|
83
|
+
# (see Interaction#delete_message)
|
84
|
+
def delete_message(message)
|
85
|
+
@interaction.delete_message(message)
|
86
|
+
end
|
87
|
+
|
88
|
+
# (see Interaction#defer_update)
|
89
|
+
def defer_update
|
90
|
+
@interaction.defer_update
|
91
|
+
end
|
92
|
+
|
93
|
+
# (see Interaction#get_component)
|
94
|
+
def get_component(custom_id)
|
95
|
+
@interaction.get_component(custom_id)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Event handler for INTERACTION_CREATE events.
|
100
|
+
class InteractionCreateEventHandler < EventHandler
|
101
|
+
# @!visibility private
|
102
|
+
def matches?(event)
|
103
|
+
return false unless event.is_a? InteractionCreateEvent
|
104
|
+
|
105
|
+
[
|
106
|
+
matches_all(@attributes[:type], event.type) do |a, e|
|
107
|
+
a == case a
|
108
|
+
when String, Symbol
|
109
|
+
Discordrb::Interactions::TYPES[e.to_sym]
|
110
|
+
else
|
111
|
+
e
|
112
|
+
end
|
113
|
+
end,
|
114
|
+
|
115
|
+
matches_all(@attributes[:server], event.interaction) do |a, e|
|
116
|
+
a.resolve_id == e.server_id
|
117
|
+
end,
|
118
|
+
|
119
|
+
matches_all(@attributes[:channel], event.interaction) do |a, e|
|
120
|
+
a.resolve_id == e.channel_id
|
121
|
+
end,
|
122
|
+
|
123
|
+
matches_all(@attributes[:user], event.user) do |a, e|
|
124
|
+
a.resolve_id == e.id
|
125
|
+
end
|
126
|
+
].reduce(true, &:&)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Event for ApplicationCommand interactions.
|
131
|
+
class ApplicationCommandEvent < InteractionCreateEvent
|
132
|
+
# Struct to allow accessing data via [] or methods.
|
133
|
+
Resolved = Struct.new('Resolved', :channels, :members, :messages, :roles, :users, :attachments) # rubocop:disable Lint/StructNewOverride
|
134
|
+
|
135
|
+
# @return [String] The name of the command.
|
136
|
+
attr_reader :command_name
|
137
|
+
|
138
|
+
# @return [Integer] The ID of the command.
|
139
|
+
attr_reader :command_id
|
140
|
+
|
141
|
+
# @return [String, nil] The name of the subcommand group relevant to this event.
|
142
|
+
attr_reader :subcommand_group
|
143
|
+
|
144
|
+
# @return [String, nil] The name of the subcommand relevant to this event.
|
145
|
+
attr_reader :subcommand
|
146
|
+
|
147
|
+
# @return [Resolved]
|
148
|
+
attr_reader :resolved
|
149
|
+
|
150
|
+
# @return [Hash<Symbol, Object>] Arguments provided to the command, mapped as `Name => Value`.
|
151
|
+
attr_reader :options
|
152
|
+
|
153
|
+
# @return [Integer, nil] The target of this command when it is a context command.
|
154
|
+
attr_reader :target_id
|
155
|
+
|
156
|
+
def initialize(data, bot)
|
157
|
+
super
|
158
|
+
|
159
|
+
command_data = data['data']
|
160
|
+
|
161
|
+
@command_id = command_data['id']
|
162
|
+
@command_name = command_data['name'].to_sym
|
163
|
+
|
164
|
+
@target_id = command_data['target_id']&.to_i
|
165
|
+
@resolved = Resolved.new({}, {}, {}, {}, {}, {})
|
166
|
+
process_resolved(command_data['resolved']) if command_data['resolved']
|
167
|
+
|
168
|
+
options = command_data['options'] || []
|
169
|
+
|
170
|
+
if options.empty?
|
171
|
+
@options = {}
|
172
|
+
return
|
173
|
+
end
|
174
|
+
|
175
|
+
case options[0]['type']
|
176
|
+
when 2
|
177
|
+
options = options[0]
|
178
|
+
@subcommand_group = options['name'].to_sym
|
179
|
+
@subcommand = options['options'][0]['name'].to_sym
|
180
|
+
options = options['options'][0]['options']
|
181
|
+
when 1
|
182
|
+
options = options[0]
|
183
|
+
@subcommand = options['name'].to_sym
|
184
|
+
options = options['options']
|
185
|
+
end
|
186
|
+
|
187
|
+
@options = transform_options_hash(options || {})
|
188
|
+
end
|
189
|
+
|
190
|
+
# @return [Message, User, nil] The target of this command, for context commands.
|
191
|
+
def target
|
192
|
+
return nil unless @target_id
|
193
|
+
|
194
|
+
@resolved.find { |data| data.key?(@target_id) }[@target_id]
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
def process_resolved(resolved_data)
|
200
|
+
resolved_data['users']&.each do |id, data|
|
201
|
+
@resolved[:users][id.to_i] = @bot.ensure_user(data)
|
202
|
+
end
|
203
|
+
|
204
|
+
resolved_data['roles']&.each do |id, data|
|
205
|
+
@resolved[:roles][id.to_i] = Discordrb::Role.new(data, @bot)
|
206
|
+
end
|
207
|
+
|
208
|
+
resolved_data['channels']&.each do |id, data|
|
209
|
+
data['guild_id'] = @interaction.server_id
|
210
|
+
@resolved[:channels][id.to_i] = Discordrb::Channel.new(data, @bot)
|
211
|
+
end
|
212
|
+
|
213
|
+
resolved_data['members']&.each do |id, data|
|
214
|
+
data['user'] = resolved_data['users'][id]
|
215
|
+
data['guild_id'] = @interaction.server_id
|
216
|
+
@resolved[:members][id.to_i] = Discordrb::Member.new(data, nil, @bot)
|
217
|
+
end
|
218
|
+
|
219
|
+
resolved_data['messages']&.each do |id, data|
|
220
|
+
@resolved[:messages][id.to_i] = Discordrb::Message.new(data, @bot)
|
221
|
+
end
|
222
|
+
|
223
|
+
resolved_data['attachments']&.each do |id, data|
|
224
|
+
@resolved[:attachments][id.to_i] = Discordrb::Attachment.new(data, nil, @bot)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def transform_options_hash(hash)
|
229
|
+
hash.to_h { |opt| [opt['name'], opt['options'] || opt['value']] }
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Event handler for ApplicationCommandEvents.
|
234
|
+
class ApplicationCommandEventHandler < EventHandler
|
235
|
+
# @return [Hash]
|
236
|
+
attr_reader :subcommands
|
237
|
+
|
238
|
+
# @!visibility private
|
239
|
+
def initialize(attributes, block)
|
240
|
+
super
|
241
|
+
|
242
|
+
@subcommands = {}
|
243
|
+
end
|
244
|
+
|
245
|
+
# @param name [Symbol, String]
|
246
|
+
# @yieldparam [SubcommandBuilder]
|
247
|
+
# @return [ApplicationCommandEventHandler]
|
248
|
+
def group(name)
|
249
|
+
raise ArgumentError, 'Unable to mix subcommands and groups' if @subcommands.any? { |_, v| v.is_a? Proc }
|
250
|
+
|
251
|
+
builder = SubcommandBuilder.new(name)
|
252
|
+
yield builder
|
253
|
+
|
254
|
+
@subcommands.merge!(builder.to_h)
|
255
|
+
self
|
256
|
+
end
|
257
|
+
|
258
|
+
# @param name [String, Symbol]
|
259
|
+
# @yieldparam [SubcommandBuilder]
|
260
|
+
# @return [ApplicationCommandEventHandler]
|
261
|
+
def subcommand(name, &block)
|
262
|
+
raise ArgumentError, 'Unable to mix subcommands and groups' if @subcommands.any? { |_, v| v.is_a? Hash }
|
263
|
+
|
264
|
+
@subcommands[name.to_sym] = block
|
265
|
+
|
266
|
+
self
|
267
|
+
end
|
268
|
+
|
269
|
+
# @!visibility private
|
270
|
+
# @param event [Event]
|
271
|
+
def call(event)
|
272
|
+
return unless matches?(event)
|
273
|
+
|
274
|
+
if event.subcommand_group
|
275
|
+
unless (cmd = @subcommands.dig(event.subcommand_group, event.subcommand))
|
276
|
+
Discordrb::LOGGER.debug("Received an event for an unhandled subcommand `#{event.command_name} #{event.subcommand_group} #{event.subcommand}'")
|
277
|
+
return
|
278
|
+
end
|
279
|
+
|
280
|
+
cmd.call(event)
|
281
|
+
elsif event.subcommand
|
282
|
+
unless (cmd = @subcommands[event.subcommand])
|
283
|
+
Discordrb::LOGGER.debug("Received an event for an unhandled subcommand `#{event.command_name} #{event.subcommand}'")
|
284
|
+
return
|
285
|
+
end
|
286
|
+
|
287
|
+
cmd.call(event)
|
288
|
+
else
|
289
|
+
@block.call(event)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# @!visibility private
|
294
|
+
def matches?(event)
|
295
|
+
return false unless event.is_a? ApplicationCommandEvent
|
296
|
+
|
297
|
+
[
|
298
|
+
matches_all(@attributes[:name], event.command_name) do |a, e|
|
299
|
+
a.to_sym == e.to_sym
|
300
|
+
end
|
301
|
+
].reduce(true, &:&)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# Builder for adding subcommands to an ApplicationCommandHandler
|
306
|
+
class SubcommandBuilder
|
307
|
+
# @!visibility private
|
308
|
+
# @param group [String, Symbol, nil]
|
309
|
+
def initialize(group = nil)
|
310
|
+
@group = group&.to_sym
|
311
|
+
@subcommands = {}
|
312
|
+
end
|
313
|
+
|
314
|
+
# @param name [Symbol, String]
|
315
|
+
# @yieldparam [ApplicationCommandEvent]
|
316
|
+
def subcommand(name, &block)
|
317
|
+
@subcommands[name.to_sym] = block
|
318
|
+
end
|
319
|
+
|
320
|
+
# @!visibility private
|
321
|
+
def to_h
|
322
|
+
@group ? { @group => @subcommands } : @subcommands
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# An event for when a user interacts with a component.
|
327
|
+
class ComponentEvent < InteractionCreateEvent
|
328
|
+
# @return [String] User provided data for this button.
|
329
|
+
attr_reader :custom_id
|
330
|
+
|
331
|
+
# @return [Interactions::Message, nil] The message the button originates from.
|
332
|
+
attr_reader :message
|
333
|
+
|
334
|
+
# @!visibility private
|
335
|
+
def initialize(data, bot)
|
336
|
+
super
|
337
|
+
|
338
|
+
@message = Discordrb::Interactions::Message.new(data['message'], bot, @interaction) if data['message']
|
339
|
+
@custom_id = data['data']['custom_id']
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# Generic handler for component events.
|
344
|
+
class ComponentEventHandler < InteractionCreateEventHandler
|
345
|
+
def matches?(event)
|
346
|
+
return false unless super
|
347
|
+
return false unless event.is_a? ComponentEvent
|
348
|
+
|
349
|
+
[
|
350
|
+
matches_all(@attributes[:custom_id], event.custom_id) do |a, e|
|
351
|
+
# Match regexp and strings
|
352
|
+
case a
|
353
|
+
when Regexp
|
354
|
+
a.match?(e)
|
355
|
+
else
|
356
|
+
a == e
|
357
|
+
end
|
358
|
+
end,
|
359
|
+
matches_all(@attributes[:message], event.message) do |a, e|
|
360
|
+
case a
|
361
|
+
when String, Integer
|
362
|
+
a.resolve_id == e.id
|
363
|
+
else
|
364
|
+
a.id == e.id
|
365
|
+
end
|
366
|
+
end
|
367
|
+
].reduce(&:&)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
# An event for when a user interacts with a button component.
|
372
|
+
class ButtonEvent < ComponentEvent
|
373
|
+
end
|
374
|
+
|
375
|
+
# Event handler for a Button interaction event.
|
376
|
+
class ButtonEventHandler < ComponentEventHandler
|
377
|
+
end
|
378
|
+
|
379
|
+
# Event for when a user interacts with a select string component.
|
380
|
+
class StringSelectEvent < ComponentEvent
|
381
|
+
# @return [Array<String>] Selected values.
|
382
|
+
attr_reader :values
|
383
|
+
|
384
|
+
# @!visibility private
|
385
|
+
def initialize(data, bot)
|
386
|
+
super
|
387
|
+
|
388
|
+
@values = data['data']['values']
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# Event handler for a select string component.
|
393
|
+
class StringSelectEventHandler < ComponentEventHandler
|
394
|
+
end
|
395
|
+
|
396
|
+
# An event for when a user submits a modal.
|
397
|
+
class ModalSubmitEvent < ComponentEvent
|
398
|
+
# @return [Array<TextInputComponent>]
|
399
|
+
attr_reader :components
|
400
|
+
|
401
|
+
# Get the value of an input passed to the modal.
|
402
|
+
# @param custom_id [String] The custom ID of the component to look for.
|
403
|
+
# @return [String, nil]
|
404
|
+
def value(custom_id)
|
405
|
+
get_component(custom_id)&.value
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# Event handler for a modal submission.
|
410
|
+
class ModalSubmitEventHandler < ComponentEventHandler
|
411
|
+
end
|
412
|
+
|
413
|
+
# Event for when a user interacts with a select user component.
|
414
|
+
class UserSelectEvent < ComponentEvent
|
415
|
+
# @return [Array<User>] Selected values.
|
416
|
+
attr_reader :values
|
417
|
+
|
418
|
+
# @!visibility private
|
419
|
+
def initialize(data, bot)
|
420
|
+
super
|
421
|
+
|
422
|
+
@values = data['data']['values'].map { |e| bot.user(e) }
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
# Event handler for a select user component.
|
427
|
+
class UserSelectEventHandler < ComponentEventHandler
|
428
|
+
end
|
429
|
+
|
430
|
+
# Event for when a user interacts with a select role component.
|
431
|
+
class RoleSelectEvent < ComponentEvent
|
432
|
+
# @return [Array<Role>] Selected values.
|
433
|
+
attr_reader :values
|
434
|
+
|
435
|
+
# @!visibility private
|
436
|
+
def initialize(data, bot)
|
437
|
+
super
|
438
|
+
|
439
|
+
@values = data['data']['values'].map { |e| bot.server(data['guild_id']).role(e) }
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
# Event handler for a select role component.
|
444
|
+
class RoleSelectEventHandler < ComponentEventHandler
|
445
|
+
end
|
446
|
+
|
447
|
+
# Event for when a user interacts with a select mentionable component.
|
448
|
+
class MentionableSelectEvent < ComponentEvent
|
449
|
+
# @return [Hash<Symbol => Array<User>, Symbol => Array<Role>>] Selected values.
|
450
|
+
attr_reader :values
|
451
|
+
|
452
|
+
# @!visibility private
|
453
|
+
def initialize(data, bot)
|
454
|
+
super
|
455
|
+
|
456
|
+
users = data['data']['resolved']['users'].keys.map { |e| bot.user(e) }
|
457
|
+
roles = data['data']['resolved']['roles'] ? data['data']['resolved']['roles'].keys.map { |e| bot.server(data['guild_id']).role(e) } : []
|
458
|
+
@values = { users: users, roles: roles }
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
# Event handler for a select mentionable component.
|
463
|
+
class MentionableSelectEventHandler < ComponentEventHandler
|
464
|
+
end
|
465
|
+
|
466
|
+
# Event for when a user interacts with a select channel component.
|
467
|
+
class ChannelSelectEvent < ComponentEvent
|
468
|
+
# @return [Array<Channel>] Selected values.
|
469
|
+
attr_reader :values
|
470
|
+
|
471
|
+
# @!visibility private
|
472
|
+
def initialize(data, bot)
|
473
|
+
super
|
474
|
+
|
475
|
+
@values = data['data']['values'].map { |e| bot.channel(e, bot.server(data['guild_id'])) }
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# Event handler for a select channel component.
|
480
|
+
class ChannelSelectEventHandler < ComponentEventHandler
|
481
|
+
end
|
482
|
+
end
|
@@ -17,9 +17,10 @@ module Discordrb::Events
|
|
17
17
|
# @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`
|
18
18
|
# @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
|
19
19
|
# @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any.
|
20
|
+
# @param components [View, Array<Hash>, nil] A collection of components to attach to the message.
|
20
21
|
# @return [Discordrb::Message] the message that was sent
|
21
|
-
def send_message(content, tts = false, embed = nil, attachments = nil, allowed_mentions = nil, message_reference = nil)
|
22
|
-
channel.send_message(content, tts, embed, attachments, allowed_mentions, message_reference)
|
22
|
+
def send_message(content, tts = false, embed = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil)
|
23
|
+
channel.send_message(content, tts, embed, attachments, allowed_mentions, message_reference, components)
|
23
24
|
end
|
24
25
|
|
25
26
|
# The same as {#send_message}, but yields a {Webhooks::Embed} for easy building of embedded content inside a block.
|
@@ -30,11 +31,12 @@ module Discordrb::Events
|
|
30
31
|
# @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
|
31
32
|
# @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
|
32
33
|
# @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any.
|
34
|
+
# @param components [View, Array<Hash>, nil] A collection of components to attach to the message.
|
33
35
|
# @yield [embed] Yields the embed to allow for easy building inside a block.
|
34
36
|
# @yieldparam embed [Discordrb::Webhooks::Embed] The embed from the parameters, or a new one.
|
35
37
|
# @return [Message] The resulting message.
|
36
|
-
def send_embed(message = '', embed = nil, attachments = nil, tts = false, allowed_mentions = nil, message_reference = nil, &block)
|
37
|
-
channel.send_embed(message, embed, attachments, tts, allowed_mentions, message_reference, &block)
|
38
|
+
def send_embed(message = '', embed = nil, attachments = nil, tts = false, allowed_mentions = nil, message_reference = nil, components = nil, &block)
|
39
|
+
channel.send_embed(message, embed, attachments, tts, allowed_mentions, message_reference, components, &block)
|
38
40
|
end
|
39
41
|
|
40
42
|
# Sends a temporary message to the channel this message was sent in, right now.
|
@@ -44,8 +46,9 @@ module Discordrb::Events
|
|
44
46
|
# @param embed [Hash, Discordrb::Webhooks::Embed, nil] The rich embed to append to this message.
|
45
47
|
# @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`
|
46
48
|
# @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
|
47
|
-
|
48
|
-
|
49
|
+
# @param components [View, Array<Hash>, nil] A collection of components to attach to the message.
|
50
|
+
def send_temporary_message(content, timeout, tts = false, embed = nil, attachments = nil, allowed_mentions = nil, components = nil)
|
51
|
+
channel.send_temporary_message(content, timeout, tts, embed, attachments, allowed_mentions, components)
|
49
52
|
end
|
50
53
|
|
51
54
|
# Adds a string to be sent after the event has finished execution. Avoids problems with rate limiting because only
|
@@ -65,28 +65,35 @@ module Discordrb::Events
|
|
65
65
|
# @return [User] the user whose status got updated.
|
66
66
|
attr_reader :user
|
67
67
|
|
68
|
-
# @return [
|
69
|
-
attr_reader :
|
68
|
+
# @return [Discordrb::Activity] The new activity
|
69
|
+
attr_reader :activity
|
70
70
|
|
71
|
-
#
|
72
|
-
|
71
|
+
# @!attribute [r] url
|
72
|
+
# @return [String] the URL to the stream
|
73
73
|
|
74
|
-
#
|
75
|
-
|
74
|
+
# @!attribute [r] details
|
75
|
+
# @return [String] what the player is currently doing (ex. game being streamed)
|
76
76
|
|
77
|
-
#
|
78
|
-
|
77
|
+
# @!attribute [r] type
|
78
|
+
# @return [Integer] the type of play. See {Discordrb::Activity}
|
79
|
+
delegate :url, :details, :type, to: :activity
|
79
80
|
|
80
|
-
|
81
|
+
# @return [Hash<Symbol, Symbol>] the current online status (`:online`, `:idle` or `:dnd`) of the user
|
82
|
+
# on various device types (`:desktop`, `:mobile`, or `:web`). The value will be `nil` if the user is offline or invisible.
|
83
|
+
attr_reader :client_status
|
84
|
+
|
85
|
+
def initialize(data, activity, bot)
|
81
86
|
@bot = bot
|
87
|
+
@activity = activity
|
82
88
|
|
83
89
|
@server = bot.server(data['guild_id'].to_i)
|
84
90
|
@user = bot.user(data['user']['id'].to_i)
|
85
|
-
@
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
91
|
+
@client_status = @user.client_status
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return [String] the name of the new game the user is playing.
|
95
|
+
def game
|
96
|
+
@activity.name
|
90
97
|
end
|
91
98
|
end
|
92
99
|
|
@@ -137,7 +137,6 @@ module Discordrb::Events
|
|
137
137
|
# Check for the proper event type
|
138
138
|
return false unless event.is_a? ReactionRemoveAllEvent
|
139
139
|
|
140
|
-
# No attributes yet as there is no property available on the event that doesn't involve doing a resolution request
|
141
140
|
[
|
142
141
|
matches_all(@attributes[:message], event.message_id) do |a, e|
|
143
142
|
a == e
|