discordrb 3.1.1 → 3.4.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.
Potentially problematic release.
This version of discordrb might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.circleci/config.yml +126 -0
- data/.codeclimate.yml +16 -0
- data/.github/CONTRIBUTING.md +13 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
- data/.github/pull_request_template.md +37 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +39 -33
- data/.travis.yml +27 -2
- data/.yardopts +1 -1
- data/CHANGELOG.md +808 -208
- data/Gemfile +4 -1
- data/LICENSE.txt +1 -1
- data/README.md +108 -53
- data/Rakefile +14 -1
- data/bin/console +1 -0
- data/bin/travis_build_docs.sh +17 -0
- data/discordrb-webhooks.gemspec +26 -0
- data/discordrb.gemspec +24 -15
- data/lib/discordrb.rb +75 -2
- data/lib/discordrb/allowed_mentions.rb +36 -0
- data/lib/discordrb/api.rb +126 -27
- data/lib/discordrb/api/channel.rb +165 -43
- data/lib/discordrb/api/invite.rb +10 -7
- data/lib/discordrb/api/server.rb +240 -61
- data/lib/discordrb/api/user.rb +26 -24
- data/lib/discordrb/api/webhook.rb +83 -0
- data/lib/discordrb/await.rb +1 -2
- data/lib/discordrb/bot.rb +417 -149
- data/lib/discordrb/cache.rb +42 -10
- data/lib/discordrb/colour_rgb.rb +43 -0
- data/lib/discordrb/commands/command_bot.rb +186 -31
- data/lib/discordrb/commands/container.rb +30 -16
- data/lib/discordrb/commands/parser.rb +102 -47
- data/lib/discordrb/commands/rate_limiter.rb +18 -17
- data/lib/discordrb/container.rb +245 -41
- data/lib/discordrb/data.rb +27 -2511
- data/lib/discordrb/data/activity.rb +264 -0
- data/lib/discordrb/data/application.rb +50 -0
- data/lib/discordrb/data/attachment.rb +56 -0
- data/lib/discordrb/data/audit_logs.rb +345 -0
- data/lib/discordrb/data/channel.rb +849 -0
- data/lib/discordrb/data/embed.rb +251 -0
- data/lib/discordrb/data/emoji.rb +82 -0
- data/lib/discordrb/data/integration.rb +83 -0
- data/lib/discordrb/data/invite.rb +137 -0
- data/lib/discordrb/data/member.rb +297 -0
- data/lib/discordrb/data/message.rb +334 -0
- data/lib/discordrb/data/overwrite.rb +102 -0
- data/lib/discordrb/data/profile.rb +91 -0
- data/lib/discordrb/data/reaction.rb +33 -0
- data/lib/discordrb/data/recipient.rb +34 -0
- data/lib/discordrb/data/role.rb +191 -0
- data/lib/discordrb/data/server.rb +1002 -0
- data/lib/discordrb/data/user.rb +204 -0
- data/lib/discordrb/data/voice_region.rb +45 -0
- data/lib/discordrb/data/voice_state.rb +41 -0
- data/lib/discordrb/data/webhook.rb +145 -0
- data/lib/discordrb/errors.rb +36 -2
- data/lib/discordrb/events/bans.rb +7 -5
- data/lib/discordrb/events/channels.rb +2 -0
- data/lib/discordrb/events/generic.rb +19 -3
- data/lib/discordrb/events/guilds.rb +129 -6
- data/lib/discordrb/events/invites.rb +125 -0
- data/lib/discordrb/events/members.rb +6 -2
- data/lib/discordrb/events/message.rb +86 -36
- data/lib/discordrb/events/presence.rb +23 -16
- data/lib/discordrb/events/raw.rb +47 -0
- data/lib/discordrb/events/reactions.rb +159 -0
- data/lib/discordrb/events/roles.rb +7 -6
- data/lib/discordrb/events/typing.rb +9 -5
- data/lib/discordrb/events/voice_server_update.rb +47 -0
- data/lib/discordrb/events/voice_state_update.rb +29 -9
- data/lib/discordrb/events/webhooks.rb +64 -0
- data/lib/discordrb/gateway.rb +219 -88
- data/lib/discordrb/id_object.rb +39 -0
- data/lib/discordrb/light.rb +1 -1
- data/lib/discordrb/light/integrations.rb +1 -1
- data/lib/discordrb/light/light_bot.rb +1 -1
- data/lib/discordrb/logger.rb +12 -11
- data/lib/discordrb/paginator.rb +57 -0
- data/lib/discordrb/permissions.rb +148 -14
- data/lib/discordrb/version.rb +1 -1
- data/lib/discordrb/voice/encoder.rb +14 -15
- data/lib/discordrb/voice/network.rb +86 -45
- data/lib/discordrb/voice/sodium.rb +96 -0
- data/lib/discordrb/voice/voice_bot.rb +52 -40
- data/lib/discordrb/webhooks.rb +12 -0
- data/lib/discordrb/websocket.rb +2 -2
- metadata +137 -34
    
        data/lib/discordrb/cache.rb
    CHANGED
    
    | @@ -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  | 
| 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. | 
| 177 | 
            -
                #    * A short invite URL with protocol (e. | 
| 178 | 
            -
                #    * A short invite URL without protocol (e. | 
| 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. | 
| 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 | 
            -
                # @ | 
| 219 | 
            -
                #  | 
| 220 | 
            -
                 | 
| 221 | 
            -
             | 
| 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  | 
| 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/ | 
| 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  | 
| 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") { | | 
| 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 { | | 
| 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? | 
| 307 | 
            -
             | 
| 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 | 
            -
                     | 
| 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 | 
| 320 | 
            -
             | 
| 321 | 
            -
             | 
| 322 | 
            -
                     | 
| 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)
         |