discordrb 3.3.0 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
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