discordrb 3.3.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +152 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  5. data/.github/pull_request_template.md +37 -0
  6. data/.github/workflows/codeql.yml +65 -0
  7. data/.markdownlint.json +4 -0
  8. data/.rubocop.yml +39 -36
  9. data/CHANGELOG.md +874 -552
  10. data/Gemfile +2 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +80 -86
  13. data/Rakefile +2 -0
  14. data/bin/console +1 -0
  15. data/discordrb-webhooks.gemspec +9 -6
  16. data/discordrb.gemspec +21 -18
  17. data/lib/discordrb/allowed_mentions.rb +36 -0
  18. data/lib/discordrb/api/application.rb +202 -0
  19. data/lib/discordrb/api/channel.rb +236 -47
  20. data/lib/discordrb/api/interaction.rb +54 -0
  21. data/lib/discordrb/api/invite.rb +5 -5
  22. data/lib/discordrb/api/server.rb +94 -66
  23. data/lib/discordrb/api/user.rb +17 -11
  24. data/lib/discordrb/api/webhook.rb +63 -6
  25. data/lib/discordrb/api.rb +55 -16
  26. data/lib/discordrb/await.rb +0 -1
  27. data/lib/discordrb/bot.rb +480 -93
  28. data/lib/discordrb/cache.rb +31 -24
  29. data/lib/discordrb/colour_rgb.rb +43 -0
  30. data/lib/discordrb/commands/command_bot.rb +35 -12
  31. data/lib/discordrb/commands/container.rb +21 -24
  32. data/lib/discordrb/commands/parser.rb +20 -20
  33. data/lib/discordrb/commands/rate_limiter.rb +4 -3
  34. data/lib/discordrb/container.rb +209 -20
  35. data/lib/discordrb/data/activity.rb +271 -0
  36. data/lib/discordrb/data/application.rb +50 -0
  37. data/lib/discordrb/data/attachment.rb +71 -0
  38. data/lib/discordrb/data/audit_logs.rb +345 -0
  39. data/lib/discordrb/data/channel.rb +993 -0
  40. data/lib/discordrb/data/component.rb +229 -0
  41. data/lib/discordrb/data/embed.rb +251 -0
  42. data/lib/discordrb/data/emoji.rb +82 -0
  43. data/lib/discordrb/data/integration.rb +122 -0
  44. data/lib/discordrb/data/interaction.rb +800 -0
  45. data/lib/discordrb/data/invite.rb +137 -0
  46. data/lib/discordrb/data/member.rb +372 -0
  47. data/lib/discordrb/data/message.rb +414 -0
  48. data/lib/discordrb/data/overwrite.rb +108 -0
  49. data/lib/discordrb/data/profile.rb +91 -0
  50. data/lib/discordrb/data/reaction.rb +33 -0
  51. data/lib/discordrb/data/recipient.rb +34 -0
  52. data/lib/discordrb/data/role.rb +248 -0
  53. data/lib/discordrb/data/server.rb +1004 -0
  54. data/lib/discordrb/data/user.rb +264 -0
  55. data/lib/discordrb/data/voice_region.rb +45 -0
  56. data/lib/discordrb/data/voice_state.rb +41 -0
  57. data/lib/discordrb/data/webhook.rb +238 -0
  58. data/lib/discordrb/data.rb +28 -4180
  59. data/lib/discordrb/errors.rb +46 -4
  60. data/lib/discordrb/events/bans.rb +7 -5
  61. data/lib/discordrb/events/channels.rb +3 -1
  62. data/lib/discordrb/events/guilds.rb +16 -9
  63. data/lib/discordrb/events/interactions.rb +482 -0
  64. data/lib/discordrb/events/invites.rb +125 -0
  65. data/lib/discordrb/events/members.rb +6 -2
  66. data/lib/discordrb/events/message.rb +72 -27
  67. data/lib/discordrb/events/presence.rb +35 -18
  68. data/lib/discordrb/events/raw.rb +1 -3
  69. data/lib/discordrb/events/reactions.rb +49 -4
  70. data/lib/discordrb/events/threads.rb +96 -0
  71. data/lib/discordrb/events/typing.rb +6 -4
  72. data/lib/discordrb/events/voice_server_update.rb +47 -0
  73. data/lib/discordrb/events/voice_state_update.rb +15 -10
  74. data/lib/discordrb/events/webhooks.rb +9 -6
  75. data/lib/discordrb/gateway.rb +99 -71
  76. data/lib/discordrb/id_object.rb +39 -0
  77. data/lib/discordrb/light/integrations.rb +1 -1
  78. data/lib/discordrb/light/light_bot.rb +1 -1
  79. data/lib/discordrb/logger.rb +4 -4
  80. data/lib/discordrb/paginator.rb +57 -0
  81. data/lib/discordrb/permissions.rb +159 -39
  82. data/lib/discordrb/version.rb +1 -1
  83. data/lib/discordrb/voice/encoder.rb +16 -7
  84. data/lib/discordrb/voice/network.rb +99 -47
  85. data/lib/discordrb/voice/sodium.rb +98 -0
  86. data/lib/discordrb/voice/voice_bot.rb +33 -25
  87. data/lib/discordrb/webhooks.rb +2 -0
  88. data/lib/discordrb.rb +107 -1
  89. metadata +126 -54
  90. data/.codeclimate.yml +0 -16
  91. data/.travis.yml +0 -33
  92. data/bin/travis_build_docs.sh +0 -17
  93. /data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
@@ -21,8 +21,7 @@ module Discordrb
21
21
 
22
22
  @channels = {}
23
23
  @pm_channels = {}
24
-
25
- @restricted_channels = []
24
+ @thread_members = {}
26
25
  end
27
26
 
28
27
  # Returns or caches the available voice regions
@@ -42,28 +41,21 @@ module Discordrb
42
41
  # @param id [Integer] The channel ID for which to search for.
43
42
  # @param server [Server] The server for which to search the channel for. If this isn't specified, it will be
44
43
  # inferred using the API
45
- # @return [Channel] The channel identified by the ID.
44
+ # @return [Channel, nil] The channel identified by the ID.
45
+ # @raise Discordrb::Errors::NoPermission
46
46
  def channel(id, server = nil)
47
47
  id = id.resolve_id
48
48
 
49
- raise Discordrb::Errors::NoPermission if @restricted_channels.include? id
50
-
51
49
  debug("Obtaining data for channel with id #{id}")
52
50
  return @channels[id] if @channels[id]
53
51
 
54
52
  begin
55
- begin
56
- response = API::Channel.resolve(token, id)
57
- rescue RestClient::ResourceNotFound
58
- return nil
59
- end
60
- channel = Channel.new(JSON.parse(response), self, server)
61
- @channels[id] = channel
62
- rescue Discordrb::Errors::NoPermission
63
- debug "Tried to get access to restricted channel #{id}, blacklisting it"
64
- @restricted_channels << id
65
- raise
53
+ response = API::Channel.resolve(token, id)
54
+ rescue Discordrb::Errors::UnknownChannel
55
+ return nil
66
56
  end
57
+ channel = Channel.new(JSON.parse(response), self, server)
58
+ @channels[id] = channel
67
59
  end
68
60
 
69
61
  alias_method :group_channel, :channel
@@ -79,7 +71,7 @@ module Discordrb
79
71
  LOGGER.out("Resolving user #{id}")
80
72
  begin
81
73
  response = API::User.resolve(token, id)
82
- rescue RestClient::ResourceNotFound
74
+ rescue Discordrb::Errors::UnknownUser
83
75
  return nil
84
76
  end
85
77
  user = User.new(JSON.parse(response), self)
@@ -111,7 +103,6 @@ module Discordrb
111
103
  def member(server_or_id, user_id)
112
104
  server_id = server_or_id.resolve_id
113
105
  user_id = user_id.resolve_id
114
-
115
106
  server = server_or_id.is_a?(Server) ? server_or_id : self.server(server_id)
116
107
 
117
108
  return server.member(user_id) if server.member_cached?(user_id)
@@ -119,7 +110,7 @@ module Discordrb
119
110
  LOGGER.out("Resolving member #{server_id} on server #{user_id}")
120
111
  begin
121
112
  response = API::Server.resolve_member(token, server_id, user_id)
122
- rescue RestClient::ResourceNotFound
113
+ rescue Discordrb::Errors::UnknownUser, Discordrb::Errors::UnknownMember
123
114
  return nil
124
115
  end
125
116
  member = Member.new(JSON.parse(response), server, self)
@@ -134,6 +125,7 @@ module Discordrb
134
125
  def pm_channel(id)
135
126
  id = id.resolve_id
136
127
  return @pm_channels[id] if @pm_channels[id]
128
+
137
129
  debug("Creating pm channel with user id #{id}")
138
130
  response = API::User.create_pm(token, id)
139
131
  channel = Channel.new(JSON.parse(response), self)
@@ -155,10 +147,14 @@ module Discordrb
155
147
 
156
148
  # Ensures a given server object is cached and if not, cache it from the given data hash.
157
149
  # @param data [Hash] A data hash representing a server.
150
+ # @param force_cache [true, false] Whether the object in cache should be updated with the given
151
+ # data if it already exists.
158
152
  # @return [Server] the server represented by the data hash.
159
- def ensure_server(data)
153
+ def ensure_server(data, force_cache = false)
160
154
  if @servers.include?(data['id'].to_i)
161
- @servers[data['id'].to_i]
155
+ server = @servers[data['id'].to_i]
156
+ server.update_data(data) if force_cache
157
+ server
162
158
  else
163
159
  @servers[data['id'].to_i] = Server.new(data, self)
164
160
  end
@@ -176,6 +172,16 @@ module Discordrb
176
172
  end
177
173
  end
178
174
 
175
+ # Ensures a given thread member object is cached.
176
+ # @param data [Hash] Thread member data.
177
+ def ensure_thread_member(data)
178
+ thread_id = data['id'].to_i
179
+ user_id = data['user_id'].to_i
180
+
181
+ @thread_members[thread_id] ||= {}
182
+ @thread_members[thread_id][user_id] = data.slice('join_timestamp', 'flags')
183
+ end
184
+
179
185
  # Requests member chunks for a given server ID.
180
186
  # @param id [Integer] The server ID to request chunks for.
181
187
  def request_chunks(id)
@@ -187,13 +193,13 @@ module Discordrb
187
193
  #
188
194
  # * An {Invite} object
189
195
  # * The code for an invite
190
- # * A fully qualified invite URL (e.g. `https://discordapp.com/invite/0A37aN7fasF7n83q`)
196
+ # * A fully qualified invite URL (e.g. `https://discord.com/invite/0A37aN7fasF7n83q`)
191
197
  # * A short invite URL with protocol (e.g. `https://discord.gg/0A37aN7fasF7n83q`)
192
198
  # * A short invite URL without protocol (e.g. `discord.gg/0A37aN7fasF7n83q`)
193
199
  # @return [String] Only the code for the invite.
194
200
  def resolve_invite_code(invite)
195
201
  invite = invite.code if invite.is_a? Discordrb::Invite
196
- invite = invite[invite.rindex('/') + 1..-1] if invite.start_with?('http', 'discord.gg')
202
+ invite = invite[invite.rindex('/') + 1..] if invite.start_with?('http', 'discord.gg')
197
203
  invite
198
204
  end
199
205
 
@@ -219,7 +225,7 @@ module Discordrb
219
225
  return [channel(id)]
220
226
  end
221
227
 
222
- @servers.values.each do |server|
228
+ @servers.each_value do |server|
223
229
  server.channels.each do |channel|
224
230
  results << channel if channel.name == channel_name && (server_name || server.name) == server.name && (!type || (channel.type == type))
225
231
  end
@@ -248,6 +254,7 @@ module Discordrb
248
254
  def find_user(username, discrim = nil)
249
255
  users = @users.values.find_all { |e| e.username == username }
250
256
  return users.find { |u| u.discrim == discrim } if discrim
257
+
251
258
  users
252
259
  end
253
260
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # A colour (red, green and blue values). Used for role colours. If you prefer the American spelling, the alias
5
+ # {ColorRGB} is also available.
6
+ class ColourRGB
7
+ # @return [Integer] the red part of this colour (0-255).
8
+ attr_reader :red
9
+
10
+ # @return [Integer] the green part of this colour (0-255).
11
+ attr_reader :green
12
+
13
+ # @return [Integer] the blue part of this colour (0-255).
14
+ attr_reader :blue
15
+
16
+ # @return [Integer] the colour's RGB values combined into one integer.
17
+ attr_reader :combined
18
+ alias_method :to_i, :combined
19
+
20
+ # Make a new colour from the combined value.
21
+ # @param combined [String, Integer] The colour's RGB values combined into one integer or a hexadecimal string
22
+ # @example Initialize a with a base 10 integer
23
+ # ColourRGB.new(7506394) #=> ColourRGB
24
+ # ColourRGB.new(0x7289da) #=> ColourRGB
25
+ # @example Initialize a with a hexadecimal string
26
+ # ColourRGB.new('7289da') #=> ColourRGB
27
+ def initialize(combined)
28
+ @combined = combined.is_a?(String) ? combined.to_i(16) : combined
29
+ @red = (@combined >> 16) & 0xFF
30
+ @green = (@combined >> 8) & 0xFF
31
+ @blue = @combined & 0xFF
32
+ end
33
+
34
+ # @return [String] the colour as a hexadecimal.
35
+ def hex
36
+ @combined.to_s(16)
37
+ end
38
+ alias_method :hexadecimal, :hex
39
+ end
40
+
41
+ # Alias for the class {ColourRGB}
42
+ ColorRGB = ColourRGB
43
+ end
@@ -24,7 +24,7 @@ module Discordrb::Commands
24
24
 
25
25
  # Creates a new CommandBot and logs in to Discord.
26
26
  # @param attributes [Hash] The attributes to initialize the CommandBot with.
27
- # @see {Discordrb::Bot#initialize} for other attributes that should be used to create the underlying regular bot.
27
+ # @see Discordrb::Bot#initialize Discordrb::Bot#initialize for other attributes that should be used to create the underlying regular bot.
28
28
  # @option attributes [String, Array<String>, #call] :prefix The prefix that should trigger this bot's commands. It
29
29
  # can be:
30
30
  #
@@ -39,14 +39,15 @@ module Discordrb::Commands
39
39
  # complicated dynamic prefixes (e. g. based on server), or even something else entirely (suffixes, or most
40
40
  # adventurous, infixes).
41
41
  # @option attributes [true, false] :advanced_functionality Whether to enable advanced functionality (very powerful
42
- # way to nest commands into chains, see https://github.com/meew0/discordrb/wiki/Commands#command-chain-syntax
42
+ # way to nest commands into chains, see https://github.com/shardlab/discordrb/wiki/Commands#command-chain-syntax
43
43
  # for info. Default is false.
44
44
  # @option attributes [Symbol, Array<Symbol>, false] :help_command The name of the command that displays info for
45
45
  # other commands. Use an array if you want to have aliases. Default is "help". If none should be created, use
46
46
  # `false` as the value.
47
- # @option attributes [String] :command_doesnt_exist_message The message that should be displayed if a user attempts
47
+ # @option attributes [String, #call] :command_doesnt_exist_message The message that should be displayed if a user attempts
48
48
  # to use a command that does not exist. If none is specified, no message will be displayed. In the message, you
49
- # can use the string '%command%' that will be replaced with the name of the command.
49
+ # can use the string '%command%' that will be replaced with the name of the command. Anything responding to call
50
+ # such as a proc will be called with the event, and is expected to return a String or nil.
50
51
  # @option attributes [String] :no_permission_message The message to be displayed when `NoPermission` error is raised.
51
52
  # @option attributes [true, false] :spaces_allowed Whether spaces are allowed to occur between the prefix and the
52
53
  # command. Default is false.
@@ -69,7 +70,9 @@ module Discordrb::Commands
69
70
  # @option attributes [String] :quote_end Character that should end a quoted string (see
70
71
  # :advanced_functionality). Default is '"' or the same as :quote_start. Set to an empty string to disable.
71
72
  # @option attributes [true, false] :ignore_bots Whether the bot should ignore bot accounts or not. Default is false.
72
- def initialize(attributes = {})
73
+ def initialize(**attributes)
74
+ # TODO: This needs to be revisited. undefined attributes are treated
75
+ # as explicitly passed nils.
73
76
  super(
74
77
  log_mode: attributes[:log_mode],
75
78
  token: attributes[:token],
@@ -83,7 +86,9 @@ module Discordrb::Commands
83
86
  num_shards: attributes[:num_shards],
84
87
  redact_token: attributes.key?(:redact_token) ? attributes[:redact_token] : true,
85
88
  ignore_bots: attributes[:ignore_bots],
86
- compress_mode: attributes[:compress_mode])
89
+ compress_mode: attributes[:compress_mode],
90
+ intents: attributes[:intents] || :all
91
+ )
87
92
 
88
93
  @prefix = attributes[:prefix]
89
94
  @attributes = {
@@ -140,6 +145,7 @@ module Discordrb::Commands
140
145
  }
141
146
 
142
147
  return unless @attributes[:help_command]
148
+
143
149
  command(@attributes[:help_command], max_args: 1, description: 'Shows a list of all the commands available or displays help for a specific command.', usage: 'help [command name]') do |event, command_name|
144
150
  if command_name
145
151
  command = @commands[command_name.to_sym]
@@ -148,6 +154,7 @@ module Discordrb::Commands
148
154
  command_name = command.name
149
155
  end
150
156
  return "The command `#{command_name}` does not exist!" unless command
157
+
151
158
  desc = command.attributes[:description] || '*No description available*'
152
159
  usage = command.attributes[:usage]
153
160
  parameters = command.attributes[:parameters]
@@ -206,15 +213,22 @@ module Discordrb::Commands
206
213
  def execute_command(name, event, arguments, chained = false, check_permissions = true)
207
214
  debug("Executing command #{name} with arguments #{arguments}")
208
215
  return unless @commands
216
+
209
217
  command = @commands[name]
210
218
  command = command.aliased_command if command.is_a?(CommandAlias)
211
219
  return unless !check_permissions || channels?(event.channel, @attributes[:channels]) ||
212
220
  (command && !command.attributes[:channels].nil?)
221
+
213
222
  unless command
214
- event.respond @attributes[:command_doesnt_exist_message].gsub('%command%', name.to_s) if @attributes[:command_doesnt_exist_message]
223
+ if @attributes[:command_doesnt_exist_message]
224
+ message = @attributes[:command_doesnt_exist_message]
225
+ message = message.call(event) if message.respond_to?(:call)
226
+ event.respond message.gsub('%command%', name.to_s) if message
227
+ end
215
228
  return
216
229
  end
217
230
  return unless !check_permissions || channels?(event.channel, command.attributes[:channels])
231
+
218
232
  arguments = arg_check(arguments, command.attributes[:arg_types], event.server) if check_permissions
219
233
  if (check_permissions &&
220
234
  permission?(event.author, command.attributes[:permission_level], event.server) &&
@@ -238,11 +252,13 @@ module Discordrb::Commands
238
252
  # For example, `['1', '10..14']` with types `[Integer, Range]` would turn into `[1, 10..14]`.
239
253
  def arg_check(args, types = nil, server = nil)
240
254
  return args unless types
255
+
241
256
  args.each_with_index.map do |arg, i|
242
257
  next arg if types[i].nil? || types[i] == String
258
+
243
259
  if types[i] == Integer
244
260
  begin
245
- Integer(arg)
261
+ Integer(arg, 10)
246
262
  rescue ArgumentError
247
263
  nil
248
264
  end
@@ -304,7 +320,7 @@ module Discordrb::Commands
304
320
  elsif types[i].respond_to?(:from_argument)
305
321
  begin
306
322
  types[i].from_argument arg
307
- rescue
323
+ rescue StandardError
308
324
  nil
309
325
  end
310
326
  else
@@ -319,8 +335,9 @@ module Discordrb::Commands
319
335
  # @return [String, nil] the command's result, if there is any.
320
336
  def simple_execute(chain, event)
321
337
  return nil if chain.empty?
338
+
322
339
  args = chain.split(' ')
323
- execute_command(args[0].to_sym, event, args[1..-1])
340
+ execute_command(args[0].to_sym, event, args[1..])
324
341
  end
325
342
 
326
343
  # Sets the permission level of a user
@@ -369,6 +386,7 @@ module Discordrb::Commands
369
386
  # @param channel [String, Integer, Channel] The channel name, integer ID, or `Channel` object to be added
370
387
  def add_channel(channel)
371
388
  return if @attributes[:channels].find { |c| channel.resolve_id == c.resolve_id }
389
+
372
390
  @attributes[:channels] << channel
373
391
  end
374
392
 
@@ -426,7 +444,8 @@ module Discordrb::Commands
426
444
 
427
445
  def standard_prefix_trigger(message, prefix)
428
446
  return nil unless message.start_with? prefix
429
- message[prefix.length..-1]
447
+
448
+ message[prefix.length..]
430
449
  end
431
450
 
432
451
  def required_permissions?(member, required, channel = nil)
@@ -437,11 +456,13 @@ module Discordrb::Commands
437
456
 
438
457
  def required_roles?(member, required)
439
458
  return true if member.webhook? || member.is_a?(Discordrb::Recipient) || required.nil? || required.empty?
459
+
440
460
  required.is_a?(Array) ? check_multiple_roles(member, required) : member.role?(role)
441
461
  end
442
462
 
443
463
  def allowed_roles?(member, required)
444
464
  return true if member.webhook? || member.is_a?(Discordrb::Recipient) || required.nil? || required.empty?
465
+
445
466
  required.is_a?(Array) ? check_multiple_roles(member, required, false) : member.role?(role)
446
467
  end
447
468
 
@@ -459,9 +480,11 @@ module Discordrb::Commands
459
480
 
460
481
  def channels?(channel, channels)
461
482
  return true if channels.nil? || channels.empty?
483
+
462
484
  channels.any? do |c|
463
485
  # if c is string, make sure to remove the "#" from channel names in case it was specified
464
486
  return true if c.is_a?(String) && c.delete('#') == channel.name
487
+
465
488
  c.resolve_id == channel.resolve_id
466
489
  end
467
490
  end
@@ -480,7 +503,7 @@ module Discordrb::Commands
480
503
  else
481
504
  event.respond result unless result.nil? || result.empty?
482
505
  end
483
- rescue => e
506
+ rescue StandardError => e
484
507
  log_exception(e)
485
508
  ensure
486
509
  @event_threads.delete(t)
@@ -9,22 +9,25 @@ module Discordrb::Commands
9
9
  module CommandContainer
10
10
  include RateLimiter
11
11
 
12
- # @return [Hash<Symbol, Command>] hash of command names and commands this container has.
12
+ # @return [Hash<Symbol, Command, CommandAlias>] hash of command names and commands this container has.
13
13
  attr_reader :commands
14
14
 
15
15
  # Adds a new command to the container.
16
- # @param name [Symbol, Array<Symbol>] The name of the command to add, or an array of multiple names for the command
16
+ # @param name [Symbol] The name of the command to add.
17
17
  # @param attributes [Hash] The attributes to initialize the command with.
18
+ # @option attributes [Array<Symbol>] :aliases A list of additional names for this command. This in effect
19
+ # creates {CommandAlias} objects in the container ({#commands}) that refer to the newly created command.
20
+ # Additionally, the default help command will identify these command names as an alias where applicable.
18
21
  # @option attributes [Integer] :permission_level The minimum permission level that can use this command, inclusive.
19
22
  # See {CommandBot#set_user_permission} and {CommandBot#set_role_permission}.
20
23
  # @option attributes [String, false] :permission_message Message to display when a user does not have sufficient
21
24
  # permissions to execute a command. %name% in the message will be replaced with the name of the command. Disable
22
25
  # the message by setting this option to false.
23
26
  # @option attributes [Array<Symbol>] :required_permissions Discord action permissions (e.g. `:kick_members`) that
24
- # should be required to use this command. See {Discordrb::Permissions::Flags} for a list.
25
- # @option attributes [Array<Role>, Array<#resolve_id>] :required_roles Roles that user must have to use this command
27
+ # should be required to use this command. See {Discordrb::Permissions::FLAGS} for a list.
28
+ # @option attributes [Array<Role>, Array<String, Integer>] :required_roles Roles, or their IDs, that user must have to use this command
26
29
  # (user must have all of them).
27
- # @option attributes [Array<Role>, Array<#resolve_id>] :allowed_roles Roles that user should have to use this command
30
+ # @option attributes [Array<Role>, Array<String, Integer>] :allowed_roles Roles, or their IDs, that user should have to use this command
28
31
  # (user should have at least one of them).
29
32
  # @option attributes [Array<String, Integer, Channel>] :channels The channels that this command can be used on. An
30
33
  # empty array indicates it can be used on any channel. Supersedes the command bot attribute.
@@ -55,26 +58,22 @@ module Discordrb::Commands
55
58
  # @note `LocalJumpError`s are rescued from internally, giving bots the opportunity to use `return` or `break` in
56
59
  # their blocks without propagating an exception.
57
60
  # @return [Command] The command that was added.
58
- # @deprecated The command name argument will no longer support arrays in the next release.
59
- # Use the `aliases` attribute instead.
60
61
  def command(name, attributes = {}, &block)
61
62
  @commands ||= {}
62
- if name.is_a? Array
63
- new_command = nil
64
63
 
65
- name.each do |e|
66
- new_command = Command.new(e, attributes, &block)
67
- @commands[e] = new_command
68
- end
64
+ # TODO: Remove in 4.0
65
+ if name.is_a?(Array)
66
+ name, *aliases = name
67
+ attributes[:aliases] = aliases if attributes[:aliases].nil?
68
+ Discordrb::LOGGER.warn("While registering command #{name.inspect}")
69
+ Discordrb::LOGGER.warn('Arrays for command aliases is removed. Please use `aliases` argument instead.')
70
+ end
69
71
 
70
- new_command
71
- else
72
- new_command = Command.new(name, attributes, &block)
73
- new_command.attributes[:aliases].each do |aliased_name|
74
- @commands[aliased_name] = CommandAlias.new(aliased_name, new_command)
75
- end
76
- @commands[name] = new_command
72
+ new_command = Command.new(name, attributes, &block)
73
+ new_command.attributes[:aliases].each do |aliased_name|
74
+ @commands[aliased_name] = CommandAlias.new(aliased_name, new_command)
77
75
  end
76
+ @commands[name] = new_command
78
77
  end
79
78
 
80
79
  # Removes a specific command from this container.
@@ -87,7 +86,7 @@ module Discordrb::Commands
87
86
  # Adds all commands from another container into this one. Existing commands will be overwritten.
88
87
  # @param container [Module] A module that `extend`s {CommandContainer} from which the commands will be added.
89
88
  def include_commands(container)
90
- handlers = container.instance_variable_get '@commands'
89
+ handlers = container.instance_variable_get :@commands
91
90
  return unless handlers
92
91
 
93
92
  @commands ||= {}
@@ -100,9 +99,7 @@ module Discordrb::Commands
100
99
  container_modules = container.singleton_class.included_modules
101
100
 
102
101
  # If the container is an EventContainer and we can include it, then do that
103
- if container_modules.include?(Discordrb::EventContainer) && respond_to?(:include_events)
104
- include_events(container)
105
- end
102
+ include_events(container) if container_modules.include?(Discordrb::EventContainer) && respond_to?(:include_events)
106
103
 
107
104
  if container_modules.include? Discordrb::Commands::CommandContainer
108
105
  include_commands(container)
@@ -81,45 +81,43 @@ module Discordrb::Commands
81
81
  # @return [String] the result of the execution.
82
82
  def call(event, arguments, chained = false, check_permissions = true)
83
83
  if arguments.length < @attributes[:min_args]
84
- event.respond "Too few arguments for command `#{name}`!"
85
- event.respond "Usage: `#{@attributes[:usage]}`" if @attributes[:usage]
84
+ response = "Too few arguments for command `#{name}`!"
85
+ response += "\nUsage: `#{@attributes[:usage]}`" if @attributes[:usage]
86
+ event.respond(response)
86
87
  return
87
88
  end
88
89
  if @attributes[:max_args] >= 0 && arguments.length > @attributes[:max_args]
89
- event.respond "Too many arguments for command `#{name}`!"
90
- event.respond "Usage: `#{@attributes[:usage]}`" if @attributes[:usage]
90
+ response = "Too many arguments for command `#{name}`!"
91
+ response += "\nUsage: `#{@attributes[:usage]}`" if @attributes[:usage]
92
+ event.respond(response)
91
93
  return
92
94
  end
93
- unless @attributes[:chain_usable]
94
- if chained
95
- event.respond "Command `#{name}` cannot be used in a command chain!"
96
- return
97
- end
95
+ unless @attributes[:chain_usable] && !chained
96
+ event.respond "Command `#{name}` cannot be used in a command chain!"
97
+ return
98
98
  end
99
99
 
100
100
  if check_permissions
101
101
  rate_limited = event.bot.rate_limited?(@attributes[:bucket], event.author)
102
102
  if @attributes[:bucket] && rate_limited
103
- if @attributes[:rate_limit_message]
104
- event.respond @attributes[:rate_limit_message].gsub('%time%', rate_limited.round(2).to_s)
105
- end
103
+ event.respond @attributes[:rate_limit_message].gsub('%time%', rate_limited.round(2).to_s) if @attributes[:rate_limit_message]
106
104
  return
107
105
  end
108
106
  end
109
107
 
110
108
  result = @block.call(event, *arguments)
111
109
  event.drain_into(result)
112
- rescue LocalJumpError => ex # occurs when breaking
113
- result = ex.exit_value
110
+ rescue LocalJumpError => e # occurs when breaking
111
+ result = e.exit_value
114
112
  event.drain_into(result)
115
- rescue => exception # Something went wrong inside our @block!
113
+ rescue StandardError => e # Something went wrong inside our @block!
116
114
  rescue_value = @attributes[:rescue] || event.bot.attributes[:rescue]
117
115
  if rescue_value
118
- event.respond(rescue_value.gsub('%exception%', exception.message)) if rescue_value.is_a?(String)
119
- rescue_value.call(event, exception) if rescue_value.respond_to?(:call)
116
+ event.respond(rescue_value.gsub('%exception%', e.message)) if rescue_value.is_a?(String)
117
+ rescue_value.call(event, e) if rescue_value.respond_to?(:call)
120
118
  end
121
119
 
122
- raise exception
120
+ raise e
123
121
  end
124
122
  end
125
123
 
@@ -209,8 +207,10 @@ module Discordrb::Commands
209
207
  result += char if b_level <= 0
210
208
 
211
209
  next unless char == @attributes[:sub_chain_end] && !quoted
210
+
212
211
  b_level -= 1
213
212
  next unless b_level.zero?
213
+
214
214
  nested = @chain[b_start + 1..index - 1]
215
215
  subchain = CommandChain.new(nested, @bot, true)
216
216
  result += subchain.execute(event)
@@ -246,7 +246,7 @@ module Discordrb::Commands
246
246
 
247
247
  first_space = command.index ' '
248
248
  command_name = first_space ? command[0..first_space - 1] : command
249
- arguments = first_space ? command[first_space + 1..-1] : ''
249
+ arguments = first_space ? command[first_space + 1..] : ''
250
250
 
251
251
  # Append a previous sign if none is present
252
252
  arguments += @attributes[:previous] unless arguments.include? @attributes[:previous]
@@ -318,7 +318,7 @@ module Discordrb::Commands
318
318
  arg.split ' '
319
319
  end
320
320
 
321
- chain = chain[chain_args_index + 1..-1]
321
+ chain = chain[chain_args_index + 1..]
322
322
  end
323
323
 
324
324
  [chain_args, chain]
@@ -35,7 +35,7 @@ module Discordrb::Commands
35
35
  end
36
36
 
37
37
  # Performs a rate limiting request
38
- # @param thing [#resolve_id, Integer, Symbol] The particular thing that should be rate-limited (usually a user/channel, but you can also choose arbitrary integers or symbols)
38
+ # @param thing [String, Integer, Symbol] The particular thing that should be rate-limited (usually a user/channel, but you can also choose arbitrary integers or symbols)
39
39
  # @param rate_limit_time [Time] The time to base the rate limiting on, only useful for testing.
40
40
  # @param increment [Integer] How much to increment the rate-limit counter. Default is 1.
41
41
  # @return [Integer, false] the waiting time until the next request, in seconds, or false if the request succeeded
@@ -83,6 +83,7 @@ module Discordrb::Commands
83
83
  def resolve_key(thing)
84
84
  return thing.resolve_id if thing.respond_to?(:resolve_id) && !thing.is_a?(String)
85
85
  return thing if thing.is_a?(Integer) || thing.is_a?(Symbol)
86
+
86
87
  raise ArgumentError, "Cannot use a #{thing.class} as a rate limiting key!"
87
88
  end
88
89
  end
@@ -104,7 +105,7 @@ module Discordrb::Commands
104
105
 
105
106
  # Performs a rate limit request.
106
107
  # @param key [Symbol] Which bucket to perform the request for.
107
- # @param thing [#resolve_id, Integer, Symbol] What should be rate-limited.
108
+ # @param thing [String, Integer, Symbol] What should be rate-limited.
108
109
  # @param increment (see Bucket#rate_limited?)
109
110
  # @see Bucket#rate_limited?
110
111
  # @return [Integer, false] How much time to wait or false if the request succeeded.
@@ -124,7 +125,7 @@ module Discordrb::Commands
124
125
  # Adds all the buckets from another RateLimiter onto this one.
125
126
  # @param limiter [Module] Another {RateLimiter} module
126
127
  def include_buckets(limiter)
127
- buckets = limiter.instance_variable_get('@buckets') || {}
128
+ buckets = limiter.instance_variable_get(:@buckets) || {}
128
129
  @buckets ||= {}
129
130
  @buckets.merge! buckets
130
131
  end