Wednesday, July 15, 2026 2:53:51 AM
> application_command.rb
# frozen_string_literal: true

module ESM
  module Discord
    module Event
      #
      # Delegator over Discordrb::Events::ApplicationCommandEvent
      #
      class ApplicationCommand
        delegate :user, :options, :respond, :edit_response, :delete_response, to: :@event

        def initialize(event)
          @event = event

          @tip_key = "tip_counter"
          @tip = ESM.config.tips.sample

          @embed_template =
            ESM::Embed.build do |e|
              e.color = Color::BLUE
            end

          @backtrace_cleaner = ActiveSupport::BacktraceCleaner.new
          @backtrace_cleaner.add_filter { |line| line.gsub(ESM.root.to_s, "").delete_prefix("/") }
          @backtrace_cleaner.add_silencer { |line| /gems/.match?(line) }
        end

        #
        # Discordrb's ApplicationCommandEvent code does not appear to handle if there is no server_id and crashes
        #
        def server
          return if @event.server_id.nil?

          @event.server
        end

        #
        # discordrb 3.8+ only populates Interaction#channel from a nested `channel` object in the
        # interaction payload. Fall back to the bot's channel cache using channel_id when it isn't present.
        #
        def channel
          @event.channel || ESM.discord_bot.channel(@event.channel_id)
        end

        def on_execution(command_class)
          respond(content: "Processing your request...")

          origin = ESM::Discord::Command::Origin.new(
            user: ESM::User.from_discord(user),
            community: ESM::Community.from_discord(server),
            channel: channel
          )

          @command = command_class.new(arguments: options, origin: origin)

          ESM::Database.with_connection do
            @command.from_discord!
          end

          on_completion
        rescue => error
          on_error(error)
        end

        def on_completion
          content = ":stopwatch: Completed in #{@command.timers.humanized_total}"
          content = add_tip(content)

          edit_response(content: content)

          @command
        end

        def on_error(error)
          return delete_response if error.is_a?(Exception::CheckFailureNoMessage)

          content =
            if error.is_a?(Exception::ApplicationError)
              ":warning: I was unable to complete your request :warning:\n"
            else
              ":grimacing: Well, this is awkward... :grimacing:\n"
            end

          edit_response(content:)

          message =
            case error
            when ESM::Exception::RequestTimeout
              ESM::Embed.build(
                :error,
                description: I18n.t(
                  "exceptions.extension.message_undeliverable",
                  user: @command.current_user.mention,
                  server_id: @command.target_server.server_id
                )
              )
            when ESM::Exception::ApplicationError
              error.to_embed
            when StandardError
              uuid = SecureRandom.uuid.split("-")[0..1].join("")

              ESM.discord_bot.log_error(
                uuid:,
                user: @command.current_user.attributes_for_logging,
                message: error.inspect,
                backtrace: @backtrace_cleaner.clean(error.backtrace)
              )

              ESM::Embed.build(:error, description: I18n.t("exceptions.system", error_code: uuid))
            end

          ESM.discord_bot.deliver(message, to: channel)

          @command
        end

        def send_tip?
          @send_tip ||= lambda do
            counter = ESM.cache.increment(@tip_key)
            send_tip = counter > (1 + (1 + rand + rand).round)

            ESM.cache.delete(@tip_key) if send_tip # Reset the counter

            send_tip
          end.call
        end

        def add_tip(content)
          return content unless send_tip?

          content + "\n\n:information_source: **Did you know?**\n#{@tip}"
        end
      end
    end
  end
end
All opinions represented herein are my own
- © 2024 - 2026 itsthedevman
- build 43aa0d7