discordrb 3.1.1 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of discordrb might be problematic. Click here for more details.

Files changed (91) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +126 -0
  3. data/.codeclimate.yml +16 -0
  4. data/.github/CONTRIBUTING.md +13 -0
  5. data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
  6. data/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
  7. data/.github/pull_request_template.md +37 -0
  8. data/.gitignore +5 -0
  9. data/.rubocop.yml +39 -33
  10. data/.travis.yml +27 -2
  11. data/.yardopts +1 -1
  12. data/CHANGELOG.md +808 -208
  13. data/Gemfile +4 -1
  14. data/LICENSE.txt +1 -1
  15. data/README.md +108 -53
  16. data/Rakefile +14 -1
  17. data/bin/console +1 -0
  18. data/bin/travis_build_docs.sh +17 -0
  19. data/discordrb-webhooks.gemspec +26 -0
  20. data/discordrb.gemspec +24 -15
  21. data/lib/discordrb.rb +75 -2
  22. data/lib/discordrb/allowed_mentions.rb +36 -0
  23. data/lib/discordrb/api.rb +126 -27
  24. data/lib/discordrb/api/channel.rb +165 -43
  25. data/lib/discordrb/api/invite.rb +10 -7
  26. data/lib/discordrb/api/server.rb +240 -61
  27. data/lib/discordrb/api/user.rb +26 -24
  28. data/lib/discordrb/api/webhook.rb +83 -0
  29. data/lib/discordrb/await.rb +1 -2
  30. data/lib/discordrb/bot.rb +417 -149
  31. data/lib/discordrb/cache.rb +42 -10
  32. data/lib/discordrb/colour_rgb.rb +43 -0
  33. data/lib/discordrb/commands/command_bot.rb +186 -31
  34. data/lib/discordrb/commands/container.rb +30 -16
  35. data/lib/discordrb/commands/parser.rb +102 -47
  36. data/lib/discordrb/commands/rate_limiter.rb +18 -17
  37. data/lib/discordrb/container.rb +245 -41
  38. data/lib/discordrb/data.rb +27 -2511
  39. data/lib/discordrb/data/activity.rb +264 -0
  40. data/lib/discordrb/data/application.rb +50 -0
  41. data/lib/discordrb/data/attachment.rb +56 -0
  42. data/lib/discordrb/data/audit_logs.rb +345 -0
  43. data/lib/discordrb/data/channel.rb +849 -0
  44. data/lib/discordrb/data/embed.rb +251 -0
  45. data/lib/discordrb/data/emoji.rb +82 -0
  46. data/lib/discordrb/data/integration.rb +83 -0
  47. data/lib/discordrb/data/invite.rb +137 -0
  48. data/lib/discordrb/data/member.rb +297 -0
  49. data/lib/discordrb/data/message.rb +334 -0
  50. data/lib/discordrb/data/overwrite.rb +102 -0
  51. data/lib/discordrb/data/profile.rb +91 -0
  52. data/lib/discordrb/data/reaction.rb +33 -0
  53. data/lib/discordrb/data/recipient.rb +34 -0
  54. data/lib/discordrb/data/role.rb +191 -0
  55. data/lib/discordrb/data/server.rb +1002 -0
  56. data/lib/discordrb/data/user.rb +204 -0
  57. data/lib/discordrb/data/voice_region.rb +45 -0
  58. data/lib/discordrb/data/voice_state.rb +41 -0
  59. data/lib/discordrb/data/webhook.rb +145 -0
  60. data/lib/discordrb/errors.rb +36 -2
  61. data/lib/discordrb/events/bans.rb +7 -5
  62. data/lib/discordrb/events/channels.rb +2 -0
  63. data/lib/discordrb/events/generic.rb +19 -3
  64. data/lib/discordrb/events/guilds.rb +129 -6
  65. data/lib/discordrb/events/invites.rb +125 -0
  66. data/lib/discordrb/events/members.rb +6 -2
  67. data/lib/discordrb/events/message.rb +86 -36
  68. data/lib/discordrb/events/presence.rb +23 -16
  69. data/lib/discordrb/events/raw.rb +47 -0
  70. data/lib/discordrb/events/reactions.rb +159 -0
  71. data/lib/discordrb/events/roles.rb +7 -6
  72. data/lib/discordrb/events/typing.rb +9 -5
  73. data/lib/discordrb/events/voice_server_update.rb +47 -0
  74. data/lib/discordrb/events/voice_state_update.rb +29 -9
  75. data/lib/discordrb/events/webhooks.rb +64 -0
  76. data/lib/discordrb/gateway.rb +219 -88
  77. data/lib/discordrb/id_object.rb +39 -0
  78. data/lib/discordrb/light.rb +1 -1
  79. data/lib/discordrb/light/integrations.rb +1 -1
  80. data/lib/discordrb/light/light_bot.rb +1 -1
  81. data/lib/discordrb/logger.rb +12 -11
  82. data/lib/discordrb/paginator.rb +57 -0
  83. data/lib/discordrb/permissions.rb +148 -14
  84. data/lib/discordrb/version.rb +1 -1
  85. data/lib/discordrb/voice/encoder.rb +14 -15
  86. data/lib/discordrb/voice/network.rb +86 -45
  87. data/lib/discordrb/voice/sodium.rb +96 -0
  88. data/lib/discordrb/voice/voice_bot.rb +52 -40
  89. data/lib/discordrb/webhooks.rb +12 -0
  90. data/lib/discordrb/websocket.rb +2 -2
  91. metadata +137 -34
@@ -15,6 +15,8 @@ module Discordrb
15
15
  def init_cache
16
16
  @users = {}
17
17
 
18
+ @voice_regions = {}
19
+
18
20
  @servers = {}
19
21
 
20
22
  @channels = {}
@@ -23,6 +25,18 @@ module Discordrb
23
25
  @restricted_channels = []
24
26
  end
25
27
 
28
+ # Returns or caches the available voice regions
29
+ def voice_regions
30
+ return @voice_regions unless @voice_regions.empty?
31
+
32
+ regions = JSON.parse API.voice_regions(token)
33
+ regions.each do |data|
34
+ @voice_regions[data['id']] = VoiceRegion.new(data)
35
+ end
36
+
37
+ @voice_regions
38
+ end
39
+
26
40
  # Gets a channel given its ID. This queries the internal channel cache, and if the channel doesn't
27
41
  # exist in there, it will get the data from Discord.
28
42
  # @param id [Integer] The channel ID for which to search for.
@@ -83,7 +97,7 @@ module Discordrb
83
97
  LOGGER.out("Resolving server #{id}")
84
98
  begin
85
99
  response = API::Server.resolve(token, id)
86
- rescue RestClient::ResourceNotFound
100
+ rescue Discordrb::Errors::NoPermission
87
101
  return nil
88
102
  end
89
103
  server = Server.new(JSON.parse(response), self)
@@ -120,6 +134,7 @@ module Discordrb
120
134
  def pm_channel(id)
121
135
  id = id.resolve_id
122
136
  return @pm_channels[id] if @pm_channels[id]
137
+
123
138
  debug("Creating pm channel with user id #{id}")
124
139
  response = API::User.create_pm(token, id)
125
140
  channel = Channel.new(JSON.parse(response), self)
@@ -173,9 +188,9 @@ module Discordrb
173
188
  #
174
189
  # * An {Invite} object
175
190
  # * The code for an invite
176
- # * A fully qualified invite URL (e. g. `https://discordapp.com/invite/0A37aN7fasF7n83q`)
177
- # * A short invite URL with protocol (e. g. `https://discord.gg/0A37aN7fasF7n83q`)
178
- # * A short invite URL without protocol (e. g. `discord.gg/0A37aN7fasF7n83q`)
191
+ # * A fully qualified invite URL (e.g. `https://discord.com/invite/0A37aN7fasF7n83q`)
192
+ # * A short invite URL with protocol (e.g. `https://discord.gg/0A37aN7fasF7n83q`)
193
+ # * A short invite URL without protocol (e.g. `discord.gg/0A37aN7fasF7n83q`)
179
194
  # @return [String] Only the code for the invite.
180
195
  def resolve_invite_code(invite)
181
196
  invite = invite.code if invite.is_a? Discordrb::Invite
@@ -205,7 +220,7 @@ module Discordrb
205
220
  return [channel(id)]
206
221
  end
207
222
 
208
- @servers.values.each do |server|
223
+ @servers.each_value do |server|
209
224
  server.channels.each do |channel|
210
225
  results << channel if channel.name == channel_name && (server_name || server.name) == server.name && (!type || (channel.type == type))
211
226
  end
@@ -214,11 +229,28 @@ module Discordrb
214
229
  results
215
230
  end
216
231
 
217
- # Finds a user given its username.
218
- # @param username [String] The username to look for.
219
- # @return [Array<User>] The array of users that were found. May be empty if none were found.
220
- def find_user(username)
221
- @users.values.find_all { |e| e.username == username }
232
+ # Finds a user given its username or username & discriminator.
233
+ # @overload find_user(username)
234
+ # Find all cached users with a certain username.
235
+ # @param username [String] The username to look for.
236
+ # @return [Array<User>] The array of users that were found. May be empty if none were found.
237
+ # @overload find_user(username, discrim)
238
+ # Find a cached user with a certain username and discriminator.
239
+ # Find a user by name and discriminator
240
+ # @param username [String] The username to look for.
241
+ # @param discrim [String] The user's discriminator
242
+ # @return [User, nil] The user that was found, or `nil` if none was found
243
+ # @note This method only searches through users that have been cached. Users that have not yet been cached
244
+ # by the bot but still share a connection with the user (mutual server) will not be found.
245
+ # @example Find users by name
246
+ # bot.find_user('z64') #=> Array<User>
247
+ # @example Find a user by name and discriminator
248
+ # bot.find_user('z64', '2639') #=> User
249
+ def find_user(username, discrim = nil)
250
+ users = @users.values.find_all { |e| e.username == username }
251
+ return users.find { |u| u.discrim == discrim } if discrim
252
+
253
+ users
222
254
  end
223
255
  end
224
256
  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
@@ -6,6 +6,7 @@ require 'discordrb/commands/parser'
6
6
  require 'discordrb/commands/events'
7
7
  require 'discordrb/commands/container'
8
8
  require 'discordrb/commands/rate_limiter'
9
+ require 'time'
9
10
 
10
11
  # Specialized bot to run commands
11
12
 
@@ -15,14 +16,15 @@ module Discordrb::Commands
15
16
  # @return [Hash] this bot's attributes.
16
17
  attr_reader :attributes
17
18
 
18
- # @return [String] the prefix commands are triggered with.
19
+ # @return [String, Array<String>, #call] the prefix commands are triggered with.
20
+ # @see #initialize
19
21
  attr_reader :prefix
20
22
 
21
23
  include CommandContainer
22
24
 
23
25
  # Creates a new CommandBot and logs in to Discord.
24
26
  # @param attributes [Hash] The attributes to initialize the CommandBot with.
25
- # @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.
26
28
  # @option attributes [String, Array<String>, #call] :prefix The prefix that should trigger this bot's commands. It
27
29
  # can be:
28
30
  #
@@ -37,7 +39,7 @@ module Discordrb::Commands
37
39
  # complicated dynamic prefixes (e. g. based on server), or even something else entirely (suffixes, or most
38
40
  # adventurous, infixes).
39
41
  # @option attributes [true, false] :advanced_functionality Whether to enable advanced functionality (very powerful
40
- # 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
41
43
  # for info. Default is false.
42
44
  # @option attributes [Symbol, Array<Symbol>, false] :help_command The name of the command that displays info for
43
45
  # other commands. Use an array if you want to have aliases. Default is "help". If none should be created, use
@@ -53,24 +55,24 @@ module Discordrb::Commands
53
55
  # @option attributes [Array<String, Integer, Channel>] :channels The channels this command bot accepts commands on.
54
56
  # Superseded if a command has a 'channels' attribute.
55
57
  # @option attributes [String] :previous Character that should designate the result of the previous command in
56
- # a command chain (see :advanced_functionality). Default is '~'.
58
+ # a command chain (see :advanced_functionality). Default is '~'. Set to an empty string to disable.
57
59
  # @option attributes [String] :chain_delimiter Character that should designate that a new command begins in the
58
- # command chain (see :advanced_functionality). Default is '>'.
60
+ # command chain (see :advanced_functionality). Default is '>'. Set to an empty string to disable.
59
61
  # @option attributes [String] :chain_args_delim Character that should separate the command chain arguments from the
60
- # chain itself (see :advanced_functionality). Default is ':'.
62
+ # chain itself (see :advanced_functionality). Default is ':'. Set to an empty string to disable.
61
63
  # @option attributes [String] :sub_chain_start Character that should start a sub-chain (see
62
- # :advanced_functionality). Default is '['.
64
+ # :advanced_functionality). Default is '['. Set to an empty string to disable.
63
65
  # @option attributes [String] :sub_chain_end Character that should end a sub-chain (see
64
- # :advanced_functionality). Default is ']'.
66
+ # :advanced_functionality). Default is ']'. Set to an empty string to disable.
65
67
  # @option attributes [String] :quote_start Character that should start a quoted string (see
66
- # :advanced_functionality). Default is '"'.
68
+ # :advanced_functionality). Default is '"'. Set to an empty string to disable.
67
69
  # @option attributes [String] :quote_end Character that should end a quoted string (see
68
- # :advanced_functionality). Default is '"'.
70
+ # :advanced_functionality). Default is '"' or the same as :quote_start. Set to an empty string to disable.
71
+ # @option attributes [true, false] :ignore_bots Whether the bot should ignore bot accounts or not. Default is false.
69
72
  def initialize(attributes = {})
70
73
  super(
71
74
  log_mode: attributes[:log_mode],
72
75
  token: attributes[:token],
73
- application_id: attributes[:application_id],
74
76
  client_id: attributes[:client_id],
75
77
  type: attributes[:type],
76
78
  name: attributes[:name],
@@ -79,7 +81,11 @@ module Discordrb::Commands
79
81
  parse_self: attributes[:parse_self],
80
82
  shard_id: attributes[:shard_id],
81
83
  num_shards: attributes[:num_shards],
82
- redact_token: attributes.key?(:redact_token) ? attributes[:redact_token] : true)
84
+ redact_token: attributes.key?(:redact_token) ? attributes[:redact_token] : true,
85
+ ignore_bots: attributes[:ignore_bots],
86
+ compress_mode: attributes[:compress_mode],
87
+ intents: attributes[:intents]
88
+ )
83
89
 
84
90
  @prefix = attributes[:prefix]
85
91
  @attributes = {
@@ -124,7 +130,10 @@ module Discordrb::Commands
124
130
  quote_start: attributes[:quote_start] || '"',
125
131
 
126
132
  # Quoted mode ending character
127
- quote_end: attributes[:quote_end] || '"'
133
+ quote_end: attributes[:quote_end] || attributes[:quote_start] || '"',
134
+
135
+ # Default block for handling internal exceptions, or a string to respond with
136
+ rescue: attributes[:rescue]
128
137
  }
129
138
 
130
139
  @permissions = {
@@ -133,14 +142,25 @@ module Discordrb::Commands
133
142
  }
134
143
 
135
144
  return unless @attributes[:help_command]
145
+
136
146
  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|
137
147
  if command_name
138
148
  command = @commands[command_name.to_sym]
149
+ if command.is_a?(CommandAlias)
150
+ command = command.aliased_command
151
+ command_name = command.name
152
+ end
139
153
  return "The command `#{command_name}` does not exist!" unless command
154
+
140
155
  desc = command.attributes[:description] || '*No description available*'
141
156
  usage = command.attributes[:usage]
142
157
  parameters = command.attributes[:parameters]
143
158
  result = "**`#{command_name}`**: #{desc}"
159
+ aliases = command_aliases(command_name.to_sym)
160
+ unless aliases.empty?
161
+ result += "\nAliases: "
162
+ result += aliases.map { |a| "`#{a.name}`" }.join(', ')
163
+ end
144
164
  result += "\nUsage: `#{usage}`" if usage
145
165
  if parameters
146
166
  result += "\nAccepted Parameters:\n```"
@@ -149,7 +169,9 @@ module Discordrb::Commands
149
169
  end
150
170
  result
151
171
  else
152
- available_commands = @commands.values.reject { |c| !c.attributes[:help_available] }
172
+ available_commands = @commands.values.reject do |c|
173
+ c.is_a?(CommandAlias) || !c.attributes[:help_available] || !required_roles?(event.user, c.attributes[:required_roles]) || !allowed_roles?(event.user, c.attributes[:allowed_roles]) || !required_permissions?(event.user, c.attributes[:required_permissions], event.channel)
174
+ end
153
175
  case available_commands.length
154
176
  when 0..5
155
177
  available_commands.reduce "**List of commands:**\n" do |memo, c|
@@ -160,13 +182,22 @@ module Discordrb::Commands
160
182
  memo + "`#{c.name}`, "
161
183
  end)[0..-3]
162
184
  else
163
- event.user.pm(available_commands.reduce("**List of commands:**\n") { |a, e| a + "`#{e.name}`, " })[0..-3]
164
- 'Sending list in PM!'
185
+ event.user.pm(available_commands.reduce("**List of commands:**\n") { |m, e| m + "`#{e.name}`, " }[0..-3])
186
+ event.channel.pm? ? '' : 'Sending list in PM!'
165
187
  end
166
188
  end
167
189
  end
168
190
  end
169
191
 
192
+ # Returns all aliases for the command with the given name
193
+ # @param name [Symbol] the name of the `Command`
194
+ # @return [Array<CommandAlias>]
195
+ def command_aliases(name)
196
+ commands.values.select do |command|
197
+ command.is_a?(CommandAlias) && command.aliased_command.name == name
198
+ end
199
+ end
200
+
170
201
  # Executes a particular command on the bot. Mostly useful for internal stuff, but one can never know.
171
202
  # @param name [Symbol] The command to execute.
172
203
  # @param event [CommandEvent] The event to pass to the command.
@@ -179,18 +210,24 @@ module Discordrb::Commands
179
210
  def execute_command(name, event, arguments, chained = false, check_permissions = true)
180
211
  debug("Executing command #{name} with arguments #{arguments}")
181
212
  return unless @commands
213
+
182
214
  command = @commands[name]
215
+ command = command.aliased_command if command.is_a?(CommandAlias)
183
216
  return unless !check_permissions || channels?(event.channel, @attributes[:channels]) ||
184
217
  (command && !command.attributes[:channels].nil?)
218
+
185
219
  unless command
186
220
  event.respond @attributes[:command_doesnt_exist_message].gsub('%command%', name.to_s) if @attributes[:command_doesnt_exist_message]
187
221
  return
188
222
  end
189
223
  return unless !check_permissions || channels?(event.channel, command.attributes[:channels])
224
+
225
+ arguments = arg_check(arguments, command.attributes[:arg_types], event.server) if check_permissions
190
226
  if (check_permissions &&
191
227
  permission?(event.author, command.attributes[:permission_level], event.server) &&
192
228
  required_permissions?(event.author, command.attributes[:required_permissions], event.channel) &&
193
- required_roles?(event.author, command.attributes[:required_roles])) ||
229
+ required_roles?(event.author, command.attributes[:required_roles]) &&
230
+ allowed_roles?(event.author, command.attributes[:allowed_roles])) ||
194
231
  !check_permissions
195
232
  event.command = command
196
233
  result = command.call(event, arguments, chained, check_permissions)
@@ -204,12 +241,94 @@ module Discordrb::Commands
204
241
  raise
205
242
  end
206
243
 
244
+ # Transforms an array of string arguments based on types array.
245
+ # For example, `['1', '10..14']` with types `[Integer, Range]` would turn into `[1, 10..14]`.
246
+ def arg_check(args, types = nil, server = nil)
247
+ return args unless types
248
+
249
+ args.each_with_index.map do |arg, i|
250
+ next arg if types[i].nil? || types[i] == String
251
+
252
+ if types[i] == Integer
253
+ begin
254
+ Integer(arg, 10)
255
+ rescue ArgumentError
256
+ nil
257
+ end
258
+ elsif types[i] == Float
259
+ begin
260
+ Float(arg)
261
+ rescue ArgumentError
262
+ nil
263
+ end
264
+ elsif types[i] == Time
265
+ begin
266
+ Time.parse arg
267
+ rescue ArgumentError
268
+ nil
269
+ end
270
+ elsif types[i] == TrueClass || types[i] == FalseClass
271
+ if arg.casecmp('true').zero? || arg.downcase.start_with?('y')
272
+ true
273
+ elsif arg.casecmp('false').zero? || arg.downcase.start_with?('n')
274
+ false
275
+ end
276
+ elsif types[i] == Symbol
277
+ arg.to_sym
278
+ elsif types[i] == Encoding
279
+ begin
280
+ Encoding.find arg
281
+ rescue ArgumentError
282
+ nil
283
+ end
284
+ elsif types[i] == Regexp
285
+ begin
286
+ Regexp.new arg
287
+ rescue ArgumentError
288
+ nil
289
+ end
290
+ elsif types[i] == Rational
291
+ begin
292
+ Rational(arg)
293
+ rescue ArgumentError
294
+ nil
295
+ end
296
+ elsif types[i] == Range
297
+ begin
298
+ if arg.include? '...'
299
+ Range.new(*arg.split('...').map(&:to_i), true)
300
+ elsif arg.include? '..'
301
+ Range.new(*arg.split('..').map(&:to_i))
302
+ end
303
+ rescue ArgumentError
304
+ nil
305
+ end
306
+ elsif types[i] == NilClass
307
+ nil
308
+ elsif [Discordrb::User, Discordrb::Role, Discordrb::Emoji].include? types[i]
309
+ result = parse_mention arg, server
310
+ result if result.instance_of? types[i]
311
+ elsif types[i] == Discordrb::Invite
312
+ resolve_invite_code arg
313
+ elsif types[i].respond_to?(:from_argument)
314
+ begin
315
+ types[i].from_argument arg
316
+ rescue StandardError
317
+ nil
318
+ end
319
+ else
320
+ raise ArgumentError, "#{types[i]} doesn't implement from_argument"
321
+ end
322
+ end
323
+ end
324
+
207
325
  # Executes a command in a simple manner, without command chains or permissions.
208
326
  # @param chain [String] The command with its arguments separated by spaces.
209
327
  # @param event [CommandEvent] The event to pass to the command.
210
328
  # @return [String, nil] the command's result, if there is any.
211
329
  def simple_execute(chain, event)
212
330
  return nil if chain.empty?
331
+
213
332
  args = chain.split(' ')
214
333
  execute_command(args[0].to_sym, event, args[1..-1])
215
334
  end
@@ -245,6 +364,31 @@ module Discordrb::Commands
245
364
  [@permissions[:users][user.id] || 0, determined_level].max >= level
246
365
  end
247
366
 
367
+ # @see CommandBot#update_channels
368
+ def channels=(channels)
369
+ update_channels(channels)
370
+ end
371
+
372
+ # Update the list of channels the bot accepts commands from.
373
+ # @param channels [Array<String, Integer, Channel>] The channels this command bot accepts commands on.
374
+ def update_channels(channels = [])
375
+ @attributes[:channels] = Array(channels)
376
+ end
377
+
378
+ # Add a channel to the list of channels the bot accepts commands from.
379
+ # @param channel [String, Integer, Channel] The channel name, integer ID, or `Channel` object to be added
380
+ def add_channel(channel)
381
+ return if @attributes[:channels].find { |c| channel.resolve_id == c.resolve_id }
382
+
383
+ @attributes[:channels] << channel
384
+ end
385
+
386
+ # Remove a channel from the list of channels the bot accepts commands from.
387
+ # @param channel [String, Integer, Channel] The channel name, integer ID, or `Channel` object to be removed
388
+ def remove_channel(channel)
389
+ @attributes[:channels].delete_if { |c| channel.resolve_id == c.resolve_id }
390
+ end
391
+
248
392
  private
249
393
 
250
394
  # Internal handler for MESSAGE_CREATE that is overwritten to allow for command handling
@@ -285,7 +429,7 @@ module Discordrb::Commands
285
429
  if @prefix.is_a? String
286
430
  standard_prefix_trigger(message.content, @prefix)
287
431
  elsif @prefix.is_a? Array
288
- @prefix.map { |e| standard_prefix_trigger(message.content, e) }.reduce { |a, e| a || e }
432
+ @prefix.map { |e| standard_prefix_trigger(message.content, e) }.reduce { |m, e| m || e }
289
433
  elsif @prefix.respond_to? :call
290
434
  @prefix.call(message)
291
435
  end
@@ -293,37 +437,48 @@ module Discordrb::Commands
293
437
 
294
438
  def standard_prefix_trigger(message, prefix)
295
439
  return nil unless message.start_with? prefix
440
+
296
441
  message[prefix.length..-1]
297
442
  end
298
443
 
299
444
  def required_permissions?(member, required, channel = nil)
300
445
  required.reduce(true) do |a, action|
301
- a && !member.webhook? && member.permission?(action, channel)
446
+ a && !member.webhook? && !member.is_a?(Discordrb::Recipient) && member.permission?(action, channel)
302
447
  end
303
448
  end
304
449
 
305
450
  def required_roles?(member, required)
306
- return (required.nil? || required.empty?) if member.webhook?
307
- if required.is_a? Array
451
+ return true if member.webhook? || member.is_a?(Discordrb::Recipient) || required.nil? || required.empty?
452
+
453
+ required.is_a?(Array) ? check_multiple_roles(member, required) : member.role?(role)
454
+ end
455
+
456
+ def allowed_roles?(member, required)
457
+ return true if member.webhook? || member.is_a?(Discordrb::Recipient) || required.nil? || required.empty?
458
+
459
+ required.is_a?(Array) ? check_multiple_roles(member, required, false) : member.role?(role)
460
+ end
461
+
462
+ def check_multiple_roles(member, required, all_roles = true)
463
+ if all_roles
308
464
  required.all? do |role|
309
465
  member.role?(role)
310
466
  end
311
467
  else
312
- member.role?(role)
468
+ required.any? do |role|
469
+ member.role?(role)
470
+ end
313
471
  end
314
472
  end
315
473
 
316
474
  def channels?(channel, channels)
317
475
  return true if channels.nil? || channels.empty?
476
+
318
477
  channels.any? do |c|
319
- if c.is_a? String
320
- # Make sure to remove the "#" from channel names in case it was specified
321
- c.delete('#') == channel.name
322
- elsif c.is_a? Fixnum
323
- c == channel.id
324
- else
325
- c == channel
326
- end
478
+ # if c is string, make sure to remove the "#" from channel names in case it was specified
479
+ return true if c.is_a?(String) && c.delete('#') == channel.name
480
+
481
+ c.resolve_id == channel.resolve_id
327
482
  end
328
483
  end
329
484
 
@@ -341,7 +496,7 @@ module Discordrb::Commands
341
496
  else
342
497
  event.respond result unless result.nil? || result.empty?
343
498
  end
344
- rescue => e
499
+ rescue StandardError => e
345
500
  log_exception(e)
346
501
  ensure
347
502
  @event_threads.delete(t)