discordrb 3.4.0 → 3.5.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +44 -18
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -1
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -1
  5. data/.github/workflows/codeql.yml +65 -0
  6. data/.markdownlint.json +4 -0
  7. data/.rubocop.yml +8 -2
  8. data/CHANGELOG.md +419 -222
  9. data/LICENSE.txt +1 -1
  10. data/README.md +37 -25
  11. data/discordrb-webhooks.gemspec +4 -1
  12. data/discordrb.gemspec +9 -6
  13. data/lib/discordrb/api/application.rb +202 -0
  14. data/lib/discordrb/api/channel.rb +182 -11
  15. data/lib/discordrb/api/interaction.rb +54 -0
  16. data/lib/discordrb/api/invite.rb +2 -2
  17. data/lib/discordrb/api/server.rb +42 -19
  18. data/lib/discordrb/api/user.rb +9 -3
  19. data/lib/discordrb/api/webhook.rb +57 -0
  20. data/lib/discordrb/api.rb +19 -5
  21. data/lib/discordrb/bot.rb +328 -33
  22. data/lib/discordrb/cache.rb +27 -22
  23. data/lib/discordrb/commands/command_bot.rb +14 -7
  24. data/lib/discordrb/commands/container.rb +1 -1
  25. data/lib/discordrb/commands/parser.rb +2 -2
  26. data/lib/discordrb/commands/rate_limiter.rb +1 -1
  27. data/lib/discordrb/container.rb +132 -3
  28. data/lib/discordrb/data/activity.rb +8 -1
  29. data/lib/discordrb/data/attachment.rb +15 -0
  30. data/lib/discordrb/data/audit_logs.rb +3 -3
  31. data/lib/discordrb/data/channel.rb +167 -23
  32. data/lib/discordrb/data/component.rb +229 -0
  33. data/lib/discordrb/data/integration.rb +42 -3
  34. data/lib/discordrb/data/interaction.rb +800 -0
  35. data/lib/discordrb/data/invite.rb +2 -2
  36. data/lib/discordrb/data/member.rb +108 -33
  37. data/lib/discordrb/data/message.rb +100 -20
  38. data/lib/discordrb/data/overwrite.rb +13 -7
  39. data/lib/discordrb/data/role.rb +58 -1
  40. data/lib/discordrb/data/server.rb +82 -80
  41. data/lib/discordrb/data/user.rb +69 -9
  42. data/lib/discordrb/data/webhook.rb +97 -4
  43. data/lib/discordrb/data.rb +3 -0
  44. data/lib/discordrb/errors.rb +44 -3
  45. data/lib/discordrb/events/channels.rb +1 -1
  46. data/lib/discordrb/events/interactions.rb +482 -0
  47. data/lib/discordrb/events/message.rb +9 -6
  48. data/lib/discordrb/events/presence.rb +21 -14
  49. data/lib/discordrb/events/reactions.rb +0 -1
  50. data/lib/discordrb/events/threads.rb +96 -0
  51. data/lib/discordrb/gateway.rb +30 -17
  52. data/lib/discordrb/permissions.rb +59 -34
  53. data/lib/discordrb/version.rb +1 -1
  54. data/lib/discordrb/voice/encoder.rb +13 -4
  55. data/lib/discordrb/voice/network.rb +18 -7
  56. data/lib/discordrb/voice/sodium.rb +3 -1
  57. data/lib/discordrb/voice/voice_bot.rb +3 -3
  58. data/lib/discordrb/webhooks.rb +2 -0
  59. data/lib/discordrb.rb +37 -4
  60. metadata +53 -19
  61. data/.codeclimate.yml +0 -16
  62. data/.travis.yml +0 -32
  63. data/bin/travis_build_docs.sh +0 -17
@@ -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
- def initialize(message)
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('@code', code)
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
@@ -31,7 +31,7 @@ module Discordrb::Events
31
31
 
32
32
  def initialize(data, bot)
33
33
  @bot = bot
34
- @channel = bot.channel(data['id'].to_i)
34
+ @channel = data.is_a?(Discordrb::Channel) ? data : bot.channel(data['id'].to_i)
35
35
  end
36
36
  end
37
37
 
@@ -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
- def send_temporary_message(content, timeout, tts = false, embed = nil, attachments = nil, allowed_mentions = nil)
48
- channel.send_temporary_message(content, timeout, tts, embed, attachments, allowed_mentions)
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 [String] the new game the user is playing.
69
- attr_reader :game
68
+ # @return [Discordrb::Activity] The new activity
69
+ attr_reader :activity
70
70
 
71
- # @return [String] the URL to the stream
72
- attr_reader :url
71
+ # @!attribute [r] url
72
+ # @return [String] the URL to the stream
73
73
 
74
- # @return [String] what the player is currently doing (ex. game being streamed)
75
- attr_reader :details
74
+ # @!attribute [r] details
75
+ # @return [String] what the player is currently doing (ex. game being streamed)
76
76
 
77
- # @return [Integer] the type of play. 0 = game, 1 = Twitch
78
- attr_reader :type
77
+ # @!attribute [r] type
78
+ # @return [Integer] the type of play. See {Discordrb::Activity}
79
+ delegate :url, :details, :type, to: :activity
79
80
 
80
- def initialize(data, bot)
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
- @game = data['game'] ? data['game']['name'] : nil
86
- @type = data['game'] ? data['game']['type'].to_i : nil
87
- # Handle optional 'game' fields safely
88
- @url = data['game'] && data['game']['url'] ? data['game']['url'] : nil
89
- @details = data['game'] && data['game']['details'] ? data['game']['details'] : nil
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