discordrb 3.3.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +152 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- data/.github/pull_request_template.md +37 -0
- data/.github/workflows/codeql.yml +65 -0
- data/.markdownlint.json +4 -0
- data/.rubocop.yml +39 -36
- data/CHANGELOG.md +874 -552
- data/Gemfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +80 -86
- data/Rakefile +2 -0
- data/bin/console +1 -0
- data/discordrb-webhooks.gemspec +9 -6
- data/discordrb.gemspec +21 -18
- data/lib/discordrb/allowed_mentions.rb +36 -0
- data/lib/discordrb/api/application.rb +202 -0
- data/lib/discordrb/api/channel.rb +236 -47
- data/lib/discordrb/api/interaction.rb +54 -0
- data/lib/discordrb/api/invite.rb +5 -5
- data/lib/discordrb/api/server.rb +94 -66
- data/lib/discordrb/api/user.rb +17 -11
- data/lib/discordrb/api/webhook.rb +63 -6
- data/lib/discordrb/api.rb +55 -16
- data/lib/discordrb/await.rb +0 -1
- data/lib/discordrb/bot.rb +480 -93
- data/lib/discordrb/cache.rb +31 -24
- data/lib/discordrb/colour_rgb.rb +43 -0
- data/lib/discordrb/commands/command_bot.rb +35 -12
- data/lib/discordrb/commands/container.rb +21 -24
- data/lib/discordrb/commands/parser.rb +20 -20
- data/lib/discordrb/commands/rate_limiter.rb +4 -3
- data/lib/discordrb/container.rb +209 -20
- data/lib/discordrb/data/activity.rb +271 -0
- data/lib/discordrb/data/application.rb +50 -0
- data/lib/discordrb/data/attachment.rb +71 -0
- data/lib/discordrb/data/audit_logs.rb +345 -0
- data/lib/discordrb/data/channel.rb +993 -0
- data/lib/discordrb/data/component.rb +229 -0
- data/lib/discordrb/data/embed.rb +251 -0
- data/lib/discordrb/data/emoji.rb +82 -0
- data/lib/discordrb/data/integration.rb +122 -0
- data/lib/discordrb/data/interaction.rb +800 -0
- data/lib/discordrb/data/invite.rb +137 -0
- data/lib/discordrb/data/member.rb +372 -0
- data/lib/discordrb/data/message.rb +414 -0
- data/lib/discordrb/data/overwrite.rb +108 -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 +248 -0
- data/lib/discordrb/data/server.rb +1004 -0
- data/lib/discordrb/data/user.rb +264 -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 +238 -0
- data/lib/discordrb/data.rb +28 -4180
- data/lib/discordrb/errors.rb +46 -4
- data/lib/discordrb/events/bans.rb +7 -5
- data/lib/discordrb/events/channels.rb +3 -1
- data/lib/discordrb/events/guilds.rb +16 -9
- data/lib/discordrb/events/interactions.rb +482 -0
- data/lib/discordrb/events/invites.rb +125 -0
- data/lib/discordrb/events/members.rb +6 -2
- data/lib/discordrb/events/message.rb +72 -27
- data/lib/discordrb/events/presence.rb +35 -18
- data/lib/discordrb/events/raw.rb +1 -3
- data/lib/discordrb/events/reactions.rb +49 -4
- data/lib/discordrb/events/threads.rb +96 -0
- data/lib/discordrb/events/typing.rb +6 -4
- data/lib/discordrb/events/voice_server_update.rb +47 -0
- data/lib/discordrb/events/voice_state_update.rb +15 -10
- data/lib/discordrb/events/webhooks.rb +9 -6
- data/lib/discordrb/gateway.rb +99 -71
- data/lib/discordrb/id_object.rb +39 -0
- data/lib/discordrb/light/integrations.rb +1 -1
- data/lib/discordrb/light/light_bot.rb +1 -1
- data/lib/discordrb/logger.rb +4 -4
- data/lib/discordrb/paginator.rb +57 -0
- data/lib/discordrb/permissions.rb +159 -39
- data/lib/discordrb/version.rb +1 -1
- data/lib/discordrb/voice/encoder.rb +16 -7
- data/lib/discordrb/voice/network.rb +99 -47
- data/lib/discordrb/voice/sodium.rb +98 -0
- data/lib/discordrb/voice/voice_bot.rb +33 -25
- data/lib/discordrb/webhooks.rb +2 -0
- data/lib/discordrb.rb +107 -1
- metadata +126 -54
- data/.codeclimate.yml +0 -16
- data/.travis.yml +0 -33
- data/bin/travis_build_docs.sh +0 -17
- /data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
    
        data/lib/discordrb/errors.rb
    CHANGED
    
    | @@ -20,6 +20,9 @@ module Discordrb | |
| 20 20 | 
             
                # Raised when the bot gets a HTTP 502 error, which is usually caused by Cloudflare.
         | 
| 21 21 | 
             
                class CloudflareError < RuntimeError; end
         | 
| 22 22 |  | 
| 23 | 
            +
                # Raised when using a webhook method without an associated token.
         | 
| 24 | 
            +
                class UnauthorizedWebhook < RuntimeError; end
         | 
| 25 | 
            +
             | 
| 23 26 | 
             
                # Generic class for errors denoted by API error codes
         | 
| 24 27 | 
             
                class CodeError < RuntimeError
         | 
| 25 28 | 
             
                  class << self
         | 
| @@ -29,8 +32,11 @@ module Discordrb | |
| 29 32 |  | 
| 30 33 | 
             
                  # Create a new error with a particular message (the code should be defined by the class instance variable)
         | 
| 31 34 | 
             
                  # @param message [String] the message to use
         | 
| 32 | 
            -
                   | 
| 35 | 
            +
                  # @param errors [Hash] API errors
         | 
| 36 | 
            +
                  def initialize(message, errors = nil)
         | 
| 33 37 | 
             
                    @message = message
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    @errors = errors ? flatten_errors(errors) : []
         | 
| 34 40 | 
             
                  end
         | 
| 35 41 |  | 
| 36 42 | 
             
                  # @return [Integer] The error code represented by this error.
         | 
| @@ -38,26 +44,62 @@ module Discordrb | |
| 38 44 | 
             
                    self.class.code
         | 
| 39 45 | 
             
                  end
         | 
| 40 46 |  | 
| 47 | 
            +
                  # @return [String] A message including the message and flattened errors.
         | 
| 48 | 
            +
                  def full_message(*)
         | 
| 49 | 
            +
                    error_list = @errors.collect { |err| "\t- #{err}" }
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    "#{@message}\n#{error_list.join("\n")}"
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 41 54 | 
             
                  # @return [String] This error's represented message
         | 
| 42 55 | 
             
                  attr_reader :message
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  # @return [Hash] More precise errors
         | 
| 58 | 
            +
                  attr_reader :errors
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  private
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  # @!visibility hidden
         | 
| 63 | 
            +
                  # Flattens errors into a more easily read format.
         | 
| 64 | 
            +
                  # @example Flattening errors of a bad field
         | 
| 65 | 
            +
                  #   flatten_errors(data['errors'])
         | 
| 66 | 
            +
                  #   # => ["embed.fields[0].name: This field is required", "embed.fields[0].value: This field is required"]
         | 
| 67 | 
            +
                  def flatten_errors(err, prev_key = nil)
         | 
| 68 | 
            +
                    err.collect do |key, sub_err|
         | 
| 69 | 
            +
                      if prev_key
         | 
| 70 | 
            +
                        key = /\A\d+\Z/.match?(key) ? "#{prev_key}[#{key}]" : "#{prev_key}.#{key}"
         | 
| 71 | 
            +
                      end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                      if (errs = sub_err['_errors'])
         | 
| 74 | 
            +
                        "#{key}: #{errs.map { |e| e['message'] }.join(' ')}"
         | 
| 75 | 
            +
                      elsif sub_err['message'] || sub_err['code']
         | 
| 76 | 
            +
                        "#{sub_err['code'] ? "#{sub_err['code']}: " : nil}#{err_msg}"
         | 
| 77 | 
            +
                      elsif sub_err.is_a? String
         | 
| 78 | 
            +
                        sub_err
         | 
| 79 | 
            +
                      else
         | 
| 80 | 
            +
                        flatten_errors(sub_err, key)
         | 
| 81 | 
            +
                      end
         | 
| 82 | 
            +
                    end.flatten
         | 
| 83 | 
            +
                  end
         | 
| 43 84 | 
             
                end
         | 
| 44 85 |  | 
| 45 86 | 
             
                # Create a new code error class
         | 
| 46 | 
            -
                # rubocop:disable  | 
| 87 | 
            +
                # rubocop:disable Naming/MethodName
         | 
| 47 88 | 
             
                def self.Code(code)
         | 
| 48 89 | 
             
                  classy = Class.new(CodeError)
         | 
| 49 | 
            -
                  classy.instance_variable_set( | 
| 90 | 
            +
                  classy.instance_variable_set(:@code, code)
         | 
| 50 91 |  | 
| 51 92 | 
             
                  @code_classes ||= {}
         | 
| 52 93 | 
             
                  @code_classes[code] = classy
         | 
| 53 94 |  | 
| 54 95 | 
             
                  classy
         | 
| 55 96 | 
             
                end
         | 
| 97 | 
            +
                # rubocop:enable Naming/MethodName
         | 
| 56 98 |  | 
| 57 99 | 
             
                # @param code [Integer] The code to check
         | 
| 58 100 | 
             
                # @return [Class] the error class for the given code
         | 
| 59 101 | 
             
                def self.error_class_for(code)
         | 
| 60 | 
            -
                  @code_classes[code]
         | 
| 102 | 
            +
                  @code_classes[code] || UnknownError
         | 
| 61 103 | 
             
                end
         | 
| 62 104 |  | 
| 63 105 | 
             
                # Used when Discord doesn't provide a more specific code
         | 
| @@ -27,20 +27,22 @@ module Discordrb::Events | |
| 27 27 |  | 
| 28 28 | 
             
                  [
         | 
| 29 29 | 
             
                    matches_all(@attributes[:user], event.user) do |a, e|
         | 
| 30 | 
            -
                       | 
| 30 | 
            +
                      case a
         | 
| 31 | 
            +
                      when String
         | 
| 31 32 | 
             
                        a == e.name
         | 
| 32 | 
            -
                       | 
| 33 | 
            +
                      when Integer
         | 
| 33 34 | 
             
                        a == e.id
         | 
| 34 | 
            -
                       | 
| 35 | 
            +
                      when :bot
         | 
| 35 36 | 
             
                        e.current_bot?
         | 
| 36 37 | 
             
                      else
         | 
| 37 38 | 
             
                        a == e
         | 
| 38 39 | 
             
                      end
         | 
| 39 40 | 
             
                    end,
         | 
| 40 41 | 
             
                    matches_all(@attributes[:server], event.server) do |a, e|
         | 
| 41 | 
            -
                      a ==  | 
| 42 | 
            +
                      a == case a
         | 
| 43 | 
            +
                           when String
         | 
| 42 44 | 
             
                             e.name
         | 
| 43 | 
            -
                            | 
| 45 | 
            +
                           when Integer
         | 
| 44 46 | 
             
                             e.id
         | 
| 45 47 | 
             
                           else
         | 
| 46 48 | 
             
                             e
         | 
| @@ -31,7 +31,7 @@ module Discordrb::Events | |
| 31 31 |  | 
| 32 32 | 
             
                def initialize(data, bot)
         | 
| 33 33 | 
             
                  @bot = bot
         | 
| 34 | 
            -
                  @channel = bot.channel(data['id'].to_i)
         | 
| 34 | 
            +
                  @channel = data.is_a?(Discordrb::Channel) ? data : bot.channel(data['id'].to_i)
         | 
| 35 35 | 
             
                end
         | 
| 36 36 | 
             
              end
         | 
| 37 37 |  | 
| @@ -126,10 +126,12 @@ module Discordrb::Events | |
| 126 126 | 
             
              class ChannelRecipientEvent < Event
         | 
| 127 127 | 
             
                # @return [Channel] the channel in question.
         | 
| 128 128 | 
             
                attr_reader :channel
         | 
| 129 | 
            +
             | 
| 129 130 | 
             
                delegate :name, :server, :type, :owner_id, :recipients, :topic, :user_limit, :position, :permission_overwrites, to: :channel
         | 
| 130 131 |  | 
| 131 132 | 
             
                # @return [Recipient] the recipient that was added/removed from the group
         | 
| 132 133 | 
             
                attr_reader :recipient
         | 
| 134 | 
            +
             | 
| 133 135 | 
             
                delegate :id, to: :recipient
         | 
| 134 136 |  | 
| 135 137 | 
             
                def initialize(data, bot)
         | 
| @@ -30,9 +30,10 @@ module Discordrb::Events | |
| 30 30 |  | 
| 31 31 | 
             
                  [
         | 
| 32 32 | 
             
                    matches_all(@attributes[:server], event.server) do |a, e|
         | 
| 33 | 
            -
                      a ==  | 
| 33 | 
            +
                      a == case a
         | 
| 34 | 
            +
                           when String
         | 
| 34 35 | 
             
                             e.name
         | 
| 35 | 
            -
                            | 
| 36 | 
            +
                           when Integer
         | 
| 36 37 | 
             
                             e.id
         | 
| 37 38 | 
             
                           else
         | 
| 38 39 | 
             
                             e
         | 
| @@ -56,12 +57,16 @@ module Discordrb::Events | |
| 56 57 | 
             
              # Event handler for {ServerUpdateEvent}
         | 
| 57 58 | 
             
              class ServerUpdateEventHandler < ServerEventHandler; end
         | 
| 58 59 |  | 
| 59 | 
            -
              # Server is deleted
         | 
| 60 | 
            +
              # Server is deleted, the server was left because the bot was kicked, or the
         | 
| 61 | 
            +
              # bot made itself leave the server.
         | 
| 60 62 | 
             
              # @see Discordrb::EventContainer#server_delete
         | 
| 61 63 | 
             
              class ServerDeleteEvent < ServerEvent
         | 
| 64 | 
            +
                # @return [Integer] The ID of the server that was left.
         | 
| 65 | 
            +
                attr_reader :server
         | 
| 66 | 
            +
             | 
| 62 67 | 
             
                # Override init_server to account for the deleted server
         | 
| 63 | 
            -
                def init_server(data,  | 
| 64 | 
            -
                  @server =  | 
| 68 | 
            +
                def init_server(data, _bot)
         | 
| 69 | 
            +
                  @server = data['id'].to_i
         | 
| 65 70 | 
             
                end
         | 
| 66 71 | 
             
              end
         | 
| 67 72 |  | 
| @@ -141,9 +146,10 @@ module Discordrb::Events | |
| 141 146 |  | 
| 142 147 | 
             
                  [
         | 
| 143 148 | 
             
                    matches_all(@attributes[:server], event.server) do |a, e|
         | 
| 144 | 
            -
                      a ==  | 
| 149 | 
            +
                      a == case a
         | 
| 150 | 
            +
                           when String
         | 
| 145 151 | 
             
                             e.name
         | 
| 146 | 
            -
                            | 
| 152 | 
            +
                           when Integer
         | 
| 147 153 | 
             
                             e.id
         | 
| 148 154 | 
             
                           else
         | 
| 149 155 | 
             
                             e
         | 
| @@ -169,9 +175,10 @@ module Discordrb::Events | |
| 169 175 |  | 
| 170 176 | 
             
                  [
         | 
| 171 177 | 
             
                    matches_all(@attributes[:server], event.server) do |a, e|
         | 
| 172 | 
            -
                      a ==  | 
| 178 | 
            +
                      a == case a
         | 
| 179 | 
            +
                           when String
         | 
| 173 180 | 
             
                             e.name
         | 
| 174 | 
            -
                            | 
| 181 | 
            +
                           when Integer
         | 
| 175 182 | 
             
                             e.id
         | 
| 176 183 | 
             
                           else
         | 
| 177 184 | 
             
                             e
         | 
| @@ -0,0 +1,482 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'discordrb/events/generic'
         | 
| 4 | 
            +
            require 'discordrb/data'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Discordrb::Events
         | 
| 7 | 
            +
              # Generic subclass for interaction events
         | 
| 8 | 
            +
              class InteractionCreateEvent < Event
         | 
| 9 | 
            +
                # @return [Interaction] The interaction for this event.
         | 
| 10 | 
            +
                attr_reader :interaction
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                # @!attribute [r] type
         | 
| 13 | 
            +
                #   @return [Integer]
         | 
| 14 | 
            +
                #   @see Interaction#type
         | 
| 15 | 
            +
                # @!attribute [r] server
         | 
| 16 | 
            +
                #   @return [Server, nil]
         | 
| 17 | 
            +
                #   @see Interaction#server
         | 
| 18 | 
            +
                # @!attribute [r] server_id
         | 
| 19 | 
            +
                #   @return [Integer]
         | 
| 20 | 
            +
                #   @see Interaction#server_id
         | 
| 21 | 
            +
                # @!attribute [r] channel
         | 
| 22 | 
            +
                #   @return [Channel]
         | 
| 23 | 
            +
                #   @see Interaction#channel
         | 
| 24 | 
            +
                # @!attribute [r] channel_id
         | 
| 25 | 
            +
                #   @return [Integer]
         | 
| 26 | 
            +
                #   @see Interaction#channel_id
         | 
| 27 | 
            +
                # @!attribute [r] user
         | 
| 28 | 
            +
                #   @return [User]
         | 
| 29 | 
            +
                #   @see Interaction#user
         | 
| 30 | 
            +
                delegate :type, :server, :server_id, :channel, :channel_id, :user, to: :interaction
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def initialize(data, bot)
         | 
| 33 | 
            +
                  @interaction = Discordrb::Interaction.new(data, bot)
         | 
| 34 | 
            +
                  @bot = bot
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                # (see Interaction#respond)
         | 
| 38 | 
            +
                def respond(content: nil, tts: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: nil, wait: false, components: nil, &block)
         | 
| 39 | 
            +
                  @interaction.respond(
         | 
| 40 | 
            +
                    content: content, tts: tts, embeds: embeds, allowed_mentions: allowed_mentions,
         | 
| 41 | 
            +
                    flags: flags, ephemeral: ephemeral, wait: wait, components: components, &block
         | 
| 42 | 
            +
                  )
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                # (see Interaction#defer)
         | 
| 46 | 
            +
                def defer(flags: 0, ephemeral: true)
         | 
| 47 | 
            +
                  @interaction.defer(flags: flags, ephemeral: ephemeral)
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                # (see Interaction#update_message)
         | 
| 51 | 
            +
                def update_message(content: nil, tts: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: nil, wait: false, components: nil, &block)
         | 
| 52 | 
            +
                  @interaction.update_message(
         | 
| 53 | 
            +
                    content: content, tts: tts, embeds: embeds, allowed_mentions: allowed_mentions,
         | 
| 54 | 
            +
                    flags: flags, ephemeral: ephemeral, wait: wait, components: components, &block
         | 
| 55 | 
            +
                  )
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                # (see Interaction#show_modal)
         | 
| 59 | 
            +
                def show_modal(title:, custom_id:, components: nil, &block)
         | 
| 60 | 
            +
                  @interaction.show_modal(title: title, custom_id: custom_id, components: components, &block)
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                # (see Interaction#edit_response)
         | 
| 64 | 
            +
                def edit_response(content: nil, embeds: nil, allowed_mentions: nil, components: nil, &block)
         | 
| 65 | 
            +
                  @interaction.edit_response(content: content, embeds: embeds, allowed_mentions: allowed_mentions, components: components, &block)
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                # (see Interaction#delete_response)
         | 
| 69 | 
            +
                def delete_response
         | 
| 70 | 
            +
                  @interaction.delete_response
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                # (see Interaction#send_message)
         | 
| 74 | 
            +
                def send_message(content: nil, embeds: nil, tts: false, allowed_mentions: nil, flags: 0, ephemeral: nil, components: nil, &block)
         | 
| 75 | 
            +
                  @interaction.send_message(content: content, embeds: embeds, tts: tts, allowed_mentions: allowed_mentions, flags: flags, ephemeral: ephemeral, components: components, &block)
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                # (see Interaction#edit_message)
         | 
| 79 | 
            +
                def edit_message(message, content: nil, embeds: nil, allowed_mentions: nil, &block)
         | 
| 80 | 
            +
                  @interaction.edit_message(message, content: content, embeds: embeds, allowed_mentions: allowed_mentions, &block)
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                # (see Interaction#delete_message)
         | 
| 84 | 
            +
                def delete_message(message)
         | 
| 85 | 
            +
                  @interaction.delete_message(message)
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                # (see Interaction#defer_update)
         | 
| 89 | 
            +
                def defer_update
         | 
| 90 | 
            +
                  @interaction.defer_update
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                # (see Interaction#get_component)
         | 
| 94 | 
            +
                def get_component(custom_id)
         | 
| 95 | 
            +
                  @interaction.get_component(custom_id)
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
              # Event handler for INTERACTION_CREATE events.
         | 
| 100 | 
            +
              class InteractionCreateEventHandler < EventHandler
         | 
| 101 | 
            +
                # @!visibility private
         | 
| 102 | 
            +
                def matches?(event)
         | 
| 103 | 
            +
                  return false unless event.is_a? InteractionCreateEvent
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  [
         | 
| 106 | 
            +
                    matches_all(@attributes[:type], event.type) do |a, e|
         | 
| 107 | 
            +
                      a == case a
         | 
| 108 | 
            +
                           when String, Symbol
         | 
| 109 | 
            +
                             Discordrb::Interactions::TYPES[e.to_sym]
         | 
| 110 | 
            +
                           else
         | 
| 111 | 
            +
                             e
         | 
| 112 | 
            +
                           end
         | 
| 113 | 
            +
                    end,
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                    matches_all(@attributes[:server], event.interaction) do |a, e|
         | 
| 116 | 
            +
                      a.resolve_id == e.server_id
         | 
| 117 | 
            +
                    end,
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    matches_all(@attributes[:channel], event.interaction) do |a, e|
         | 
| 120 | 
            +
                      a.resolve_id == e.channel_id
         | 
| 121 | 
            +
                    end,
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                    matches_all(@attributes[:user], event.user) do |a, e|
         | 
| 124 | 
            +
                      a.resolve_id == e.id
         | 
| 125 | 
            +
                    end
         | 
| 126 | 
            +
                  ].reduce(true, &:&)
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
              end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
              # Event for ApplicationCommand interactions.
         | 
| 131 | 
            +
              class ApplicationCommandEvent < InteractionCreateEvent
         | 
| 132 | 
            +
                # Struct to allow accessing data via [] or methods.
         | 
| 133 | 
            +
                Resolved = Struct.new('Resolved', :channels, :members, :messages, :roles, :users, :attachments) # rubocop:disable Lint/StructNewOverride
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                # @return [String] The name of the command.
         | 
| 136 | 
            +
                attr_reader :command_name
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                # @return [Integer] The ID of the command.
         | 
| 139 | 
            +
                attr_reader :command_id
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                # @return [String, nil] The name of the subcommand group relevant to this event.
         | 
| 142 | 
            +
                attr_reader :subcommand_group
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                # @return [String, nil] The name of the subcommand relevant to this event.
         | 
| 145 | 
            +
                attr_reader :subcommand
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                # @return [Resolved]
         | 
| 148 | 
            +
                attr_reader :resolved
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                # @return [Hash<Symbol, Object>] Arguments provided to the command, mapped as `Name => Value`.
         | 
| 151 | 
            +
                attr_reader :options
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                # @return [Integer, nil] The target of this command when it is a context command.
         | 
| 154 | 
            +
                attr_reader :target_id
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                def initialize(data, bot)
         | 
| 157 | 
            +
                  super
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                  command_data = data['data']
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                  @command_id = command_data['id']
         | 
| 162 | 
            +
                  @command_name = command_data['name'].to_sym
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                  @target_id = command_data['target_id']&.to_i
         | 
| 165 | 
            +
                  @resolved = Resolved.new({}, {}, {}, {}, {}, {})
         | 
| 166 | 
            +
                  process_resolved(command_data['resolved']) if command_data['resolved']
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                  options = command_data['options'] || []
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                  if options.empty?
         | 
| 171 | 
            +
                    @options = {}
         | 
| 172 | 
            +
                    return
         | 
| 173 | 
            +
                  end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                  case options[0]['type']
         | 
| 176 | 
            +
                  when 2
         | 
| 177 | 
            +
                    options = options[0]
         | 
| 178 | 
            +
                    @subcommand_group = options['name'].to_sym
         | 
| 179 | 
            +
                    @subcommand = options['options'][0]['name'].to_sym
         | 
| 180 | 
            +
                    options = options['options'][0]['options']
         | 
| 181 | 
            +
                  when 1
         | 
| 182 | 
            +
                    options = options[0]
         | 
| 183 | 
            +
                    @subcommand = options['name'].to_sym
         | 
| 184 | 
            +
                    options = options['options']
         | 
| 185 | 
            +
                  end
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                  @options = transform_options_hash(options || {})
         | 
| 188 | 
            +
                end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                # @return [Message, User, nil] The target of this command, for context commands.
         | 
| 191 | 
            +
                def target
         | 
| 192 | 
            +
                  return nil unless @target_id
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                  @resolved.find { |data| data.key?(@target_id) }[@target_id]
         | 
| 195 | 
            +
                end
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                private
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                def process_resolved(resolved_data)
         | 
| 200 | 
            +
                  resolved_data['users']&.each do |id, data|
         | 
| 201 | 
            +
                    @resolved[:users][id.to_i] = @bot.ensure_user(data)
         | 
| 202 | 
            +
                  end
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                  resolved_data['roles']&.each do |id, data|
         | 
| 205 | 
            +
                    @resolved[:roles][id.to_i] = Discordrb::Role.new(data, @bot)
         | 
| 206 | 
            +
                  end
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                  resolved_data['channels']&.each do |id, data|
         | 
| 209 | 
            +
                    data['guild_id'] = @interaction.server_id
         | 
| 210 | 
            +
                    @resolved[:channels][id.to_i] = Discordrb::Channel.new(data, @bot)
         | 
| 211 | 
            +
                  end
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                  resolved_data['members']&.each do |id, data|
         | 
| 214 | 
            +
                    data['user'] = resolved_data['users'][id]
         | 
| 215 | 
            +
                    data['guild_id'] = @interaction.server_id
         | 
| 216 | 
            +
                    @resolved[:members][id.to_i] = Discordrb::Member.new(data, nil, @bot)
         | 
| 217 | 
            +
                  end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                  resolved_data['messages']&.each do |id, data|
         | 
| 220 | 
            +
                    @resolved[:messages][id.to_i] = Discordrb::Message.new(data, @bot)
         | 
| 221 | 
            +
                  end
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                  resolved_data['attachments']&.each do |id, data|
         | 
| 224 | 
            +
                    @resolved[:attachments][id.to_i] = Discordrb::Attachment.new(data, nil, @bot)
         | 
| 225 | 
            +
                  end
         | 
| 226 | 
            +
                end
         | 
| 227 | 
            +
             | 
| 228 | 
            +
                def transform_options_hash(hash)
         | 
| 229 | 
            +
                  hash.to_h { |opt| [opt['name'], opt['options'] || opt['value']] }
         | 
| 230 | 
            +
                end
         | 
| 231 | 
            +
              end
         | 
| 232 | 
            +
             | 
| 233 | 
            +
              # Event handler for ApplicationCommandEvents.
         | 
| 234 | 
            +
              class ApplicationCommandEventHandler < EventHandler
         | 
| 235 | 
            +
                # @return [Hash]
         | 
| 236 | 
            +
                attr_reader :subcommands
         | 
| 237 | 
            +
             | 
| 238 | 
            +
                # @!visibility private
         | 
| 239 | 
            +
                def initialize(attributes, block)
         | 
| 240 | 
            +
                  super
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                  @subcommands = {}
         | 
| 243 | 
            +
                end
         | 
| 244 | 
            +
             | 
| 245 | 
            +
                # @param name [Symbol, String]
         | 
| 246 | 
            +
                # @yieldparam [SubcommandBuilder]
         | 
| 247 | 
            +
                # @return [ApplicationCommandEventHandler]
         | 
| 248 | 
            +
                def group(name)
         | 
| 249 | 
            +
                  raise ArgumentError, 'Unable to mix subcommands and groups' if @subcommands.any? { |_, v| v.is_a? Proc }
         | 
| 250 | 
            +
             | 
| 251 | 
            +
                  builder = SubcommandBuilder.new(name)
         | 
| 252 | 
            +
                  yield builder
         | 
| 253 | 
            +
             | 
| 254 | 
            +
                  @subcommands.merge!(builder.to_h)
         | 
| 255 | 
            +
                  self
         | 
| 256 | 
            +
                end
         | 
| 257 | 
            +
             | 
| 258 | 
            +
                # @param name [String, Symbol]
         | 
| 259 | 
            +
                # @yieldparam [SubcommandBuilder]
         | 
| 260 | 
            +
                # @return [ApplicationCommandEventHandler]
         | 
| 261 | 
            +
                def subcommand(name, &block)
         | 
| 262 | 
            +
                  raise ArgumentError, 'Unable to mix subcommands and groups' if @subcommands.any? { |_, v| v.is_a? Hash }
         | 
| 263 | 
            +
             | 
| 264 | 
            +
                  @subcommands[name.to_sym] = block
         | 
| 265 | 
            +
             | 
| 266 | 
            +
                  self
         | 
| 267 | 
            +
                end
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                # @!visibility private
         | 
| 270 | 
            +
                # @param event [Event]
         | 
| 271 | 
            +
                def call(event)
         | 
| 272 | 
            +
                  return unless matches?(event)
         | 
| 273 | 
            +
             | 
| 274 | 
            +
                  if event.subcommand_group
         | 
| 275 | 
            +
                    unless (cmd = @subcommands.dig(event.subcommand_group, event.subcommand))
         | 
| 276 | 
            +
                      Discordrb::LOGGER.debug("Received an event for an unhandled subcommand `#{event.command_name} #{event.subcommand_group} #{event.subcommand}'")
         | 
| 277 | 
            +
                      return
         | 
| 278 | 
            +
                    end
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                    cmd.call(event)
         | 
| 281 | 
            +
                  elsif event.subcommand
         | 
| 282 | 
            +
                    unless (cmd = @subcommands[event.subcommand])
         | 
| 283 | 
            +
                      Discordrb::LOGGER.debug("Received an event for an unhandled subcommand `#{event.command_name} #{event.subcommand}'")
         | 
| 284 | 
            +
                      return
         | 
| 285 | 
            +
                    end
         | 
| 286 | 
            +
             | 
| 287 | 
            +
                    cmd.call(event)
         | 
| 288 | 
            +
                  else
         | 
| 289 | 
            +
                    @block.call(event)
         | 
| 290 | 
            +
                  end
         | 
| 291 | 
            +
                end
         | 
| 292 | 
            +
             | 
| 293 | 
            +
                # @!visibility private
         | 
| 294 | 
            +
                def matches?(event)
         | 
| 295 | 
            +
                  return false unless event.is_a? ApplicationCommandEvent
         | 
| 296 | 
            +
             | 
| 297 | 
            +
                  [
         | 
| 298 | 
            +
                    matches_all(@attributes[:name], event.command_name) do |a, e|
         | 
| 299 | 
            +
                      a.to_sym == e.to_sym
         | 
| 300 | 
            +
                    end
         | 
| 301 | 
            +
                  ].reduce(true, &:&)
         | 
| 302 | 
            +
                end
         | 
| 303 | 
            +
              end
         | 
| 304 | 
            +
             | 
| 305 | 
            +
              # Builder for adding subcommands to an ApplicationCommandHandler
         | 
| 306 | 
            +
              class SubcommandBuilder
         | 
| 307 | 
            +
                # @!visibility private
         | 
| 308 | 
            +
                # @param group [String, Symbol, nil]
         | 
| 309 | 
            +
                def initialize(group = nil)
         | 
| 310 | 
            +
                  @group = group&.to_sym
         | 
| 311 | 
            +
                  @subcommands = {}
         | 
| 312 | 
            +
                end
         | 
| 313 | 
            +
             | 
| 314 | 
            +
                # @param name [Symbol, String]
         | 
| 315 | 
            +
                # @yieldparam [ApplicationCommandEvent]
         | 
| 316 | 
            +
                def subcommand(name, &block)
         | 
| 317 | 
            +
                  @subcommands[name.to_sym] = block
         | 
| 318 | 
            +
                end
         | 
| 319 | 
            +
             | 
| 320 | 
            +
                # @!visibility private
         | 
| 321 | 
            +
                def to_h
         | 
| 322 | 
            +
                  @group ? { @group => @subcommands } : @subcommands
         | 
| 323 | 
            +
                end
         | 
| 324 | 
            +
              end
         | 
| 325 | 
            +
             | 
| 326 | 
            +
              # An event for when a user interacts with a component.
         | 
| 327 | 
            +
              class ComponentEvent < InteractionCreateEvent
         | 
| 328 | 
            +
                # @return [String] User provided data for this button.
         | 
| 329 | 
            +
                attr_reader :custom_id
         | 
| 330 | 
            +
             | 
| 331 | 
            +
                # @return [Interactions::Message, nil] The message the button originates from.
         | 
| 332 | 
            +
                attr_reader :message
         | 
| 333 | 
            +
             | 
| 334 | 
            +
                # @!visibility private
         | 
| 335 | 
            +
                def initialize(data, bot)
         | 
| 336 | 
            +
                  super
         | 
| 337 | 
            +
             | 
| 338 | 
            +
                  @message = Discordrb::Interactions::Message.new(data['message'], bot, @interaction) if data['message']
         | 
| 339 | 
            +
                  @custom_id = data['data']['custom_id']
         | 
| 340 | 
            +
                end
         | 
| 341 | 
            +
              end
         | 
| 342 | 
            +
             | 
| 343 | 
            +
              # Generic handler for component events.
         | 
| 344 | 
            +
              class ComponentEventHandler < InteractionCreateEventHandler
         | 
| 345 | 
            +
                def matches?(event)
         | 
| 346 | 
            +
                  return false unless super
         | 
| 347 | 
            +
                  return false unless event.is_a? ComponentEvent
         | 
| 348 | 
            +
             | 
| 349 | 
            +
                  [
         | 
| 350 | 
            +
                    matches_all(@attributes[:custom_id], event.custom_id) do |a, e|
         | 
| 351 | 
            +
                      # Match regexp and strings
         | 
| 352 | 
            +
                      case a
         | 
| 353 | 
            +
                      when Regexp
         | 
| 354 | 
            +
                        a.match?(e)
         | 
| 355 | 
            +
                      else
         | 
| 356 | 
            +
                        a == e
         | 
| 357 | 
            +
                      end
         | 
| 358 | 
            +
                    end,
         | 
| 359 | 
            +
                    matches_all(@attributes[:message], event.message) do |a, e|
         | 
| 360 | 
            +
                      case a
         | 
| 361 | 
            +
                      when String, Integer
         | 
| 362 | 
            +
                        a.resolve_id == e.id
         | 
| 363 | 
            +
                      else
         | 
| 364 | 
            +
                        a.id == e.id
         | 
| 365 | 
            +
                      end
         | 
| 366 | 
            +
                    end
         | 
| 367 | 
            +
                  ].reduce(&:&)
         | 
| 368 | 
            +
                end
         | 
| 369 | 
            +
              end
         | 
| 370 | 
            +
             | 
| 371 | 
            +
              # An event for when a user interacts with a button component.
         | 
| 372 | 
            +
              class ButtonEvent < ComponentEvent
         | 
| 373 | 
            +
              end
         | 
| 374 | 
            +
             | 
| 375 | 
            +
              # Event handler for a Button interaction event.
         | 
| 376 | 
            +
              class ButtonEventHandler < ComponentEventHandler
         | 
| 377 | 
            +
              end
         | 
| 378 | 
            +
             | 
| 379 | 
            +
              # Event for when a user interacts with a select string component.
         | 
| 380 | 
            +
              class StringSelectEvent < ComponentEvent
         | 
| 381 | 
            +
                # @return [Array<String>] Selected values.
         | 
| 382 | 
            +
                attr_reader :values
         | 
| 383 | 
            +
             | 
| 384 | 
            +
                # @!visibility private
         | 
| 385 | 
            +
                def initialize(data, bot)
         | 
| 386 | 
            +
                  super
         | 
| 387 | 
            +
             | 
| 388 | 
            +
                  @values = data['data']['values']
         | 
| 389 | 
            +
                end
         | 
| 390 | 
            +
              end
         | 
| 391 | 
            +
             | 
| 392 | 
            +
              # Event handler for a select string component.
         | 
| 393 | 
            +
              class StringSelectEventHandler < ComponentEventHandler
         | 
| 394 | 
            +
              end
         | 
| 395 | 
            +
             | 
| 396 | 
            +
              # An event for when a user submits a modal.
         | 
| 397 | 
            +
              class ModalSubmitEvent < ComponentEvent
         | 
| 398 | 
            +
                # @return [Array<TextInputComponent>]
         | 
| 399 | 
            +
                attr_reader :components
         | 
| 400 | 
            +
             | 
| 401 | 
            +
                # Get the value of an input passed to the modal.
         | 
| 402 | 
            +
                # @param custom_id [String] The custom ID of the component to look for.
         | 
| 403 | 
            +
                # @return [String, nil]
         | 
| 404 | 
            +
                def value(custom_id)
         | 
| 405 | 
            +
                  get_component(custom_id)&.value
         | 
| 406 | 
            +
                end
         | 
| 407 | 
            +
              end
         | 
| 408 | 
            +
             | 
| 409 | 
            +
              # Event handler for a modal submission.
         | 
| 410 | 
            +
              class ModalSubmitEventHandler < ComponentEventHandler
         | 
| 411 | 
            +
              end
         | 
| 412 | 
            +
             | 
| 413 | 
            +
              # Event for when a user interacts with a select user component.
         | 
| 414 | 
            +
              class UserSelectEvent < ComponentEvent
         | 
| 415 | 
            +
                # @return [Array<User>] Selected values.
         | 
| 416 | 
            +
                attr_reader :values
         | 
| 417 | 
            +
             | 
| 418 | 
            +
                # @!visibility private
         | 
| 419 | 
            +
                def initialize(data, bot)
         | 
| 420 | 
            +
                  super
         | 
| 421 | 
            +
             | 
| 422 | 
            +
                  @values = data['data']['values'].map { |e| bot.user(e) }
         | 
| 423 | 
            +
                end
         | 
| 424 | 
            +
              end
         | 
| 425 | 
            +
             | 
| 426 | 
            +
              # Event handler for a select user component.
         | 
| 427 | 
            +
              class UserSelectEventHandler < ComponentEventHandler
         | 
| 428 | 
            +
              end
         | 
| 429 | 
            +
             | 
| 430 | 
            +
              # Event for when a user interacts with a select role component.
         | 
| 431 | 
            +
              class RoleSelectEvent < ComponentEvent
         | 
| 432 | 
            +
                # @return [Array<Role>] Selected values.
         | 
| 433 | 
            +
                attr_reader :values
         | 
| 434 | 
            +
             | 
| 435 | 
            +
                # @!visibility private
         | 
| 436 | 
            +
                def initialize(data, bot)
         | 
| 437 | 
            +
                  super
         | 
| 438 | 
            +
             | 
| 439 | 
            +
                  @values = data['data']['values'].map { |e| bot.server(data['guild_id']).role(e) }
         | 
| 440 | 
            +
                end
         | 
| 441 | 
            +
              end
         | 
| 442 | 
            +
             | 
| 443 | 
            +
              # Event handler for a select role component.
         | 
| 444 | 
            +
              class RoleSelectEventHandler < ComponentEventHandler
         | 
| 445 | 
            +
              end
         | 
| 446 | 
            +
             | 
| 447 | 
            +
              # Event for when a user interacts with a select mentionable component.
         | 
| 448 | 
            +
              class MentionableSelectEvent < ComponentEvent
         | 
| 449 | 
            +
                # @return [Hash<Symbol => Array<User>, Symbol => Array<Role>>] Selected values.
         | 
| 450 | 
            +
                attr_reader :values
         | 
| 451 | 
            +
             | 
| 452 | 
            +
                # @!visibility private
         | 
| 453 | 
            +
                def initialize(data, bot)
         | 
| 454 | 
            +
                  super
         | 
| 455 | 
            +
             | 
| 456 | 
            +
                  users   = data['data']['resolved']['users'].keys.map { |e| bot.user(e) }
         | 
| 457 | 
            +
                  roles   = data['data']['resolved']['roles'] ? data['data']['resolved']['roles'].keys.map { |e| bot.server(data['guild_id']).role(e) } : []
         | 
| 458 | 
            +
                  @values = { users: users, roles: roles }
         | 
| 459 | 
            +
                end
         | 
| 460 | 
            +
              end
         | 
| 461 | 
            +
             | 
| 462 | 
            +
              # Event handler for a select mentionable component.
         | 
| 463 | 
            +
              class MentionableSelectEventHandler < ComponentEventHandler
         | 
| 464 | 
            +
              end
         | 
| 465 | 
            +
             | 
| 466 | 
            +
              # Event for when a user interacts with a select channel component.
         | 
| 467 | 
            +
              class ChannelSelectEvent < ComponentEvent
         | 
| 468 | 
            +
                # @return [Array<Channel>] Selected values.
         | 
| 469 | 
            +
                attr_reader :values
         | 
| 470 | 
            +
             | 
| 471 | 
            +
                # @!visibility private
         | 
| 472 | 
            +
                def initialize(data, bot)
         | 
| 473 | 
            +
                  super
         | 
| 474 | 
            +
             | 
| 475 | 
            +
                  @values = data['data']['values'].map { |e| bot.channel(e, bot.server(data['guild_id'])) }
         | 
| 476 | 
            +
                end
         | 
| 477 | 
            +
              end
         | 
| 478 | 
            +
             | 
| 479 | 
            +
              # Event handler for a select channel component.
         | 
| 480 | 
            +
              class ChannelSelectEventHandler < ComponentEventHandler
         | 
| 481 | 
            +
              end
         | 
| 482 | 
            +
            end
         |