Wednesday, July 15, 2026 4:56:13 AM
> lifecycle.rb
# frozen_string_literal: true

module ESM
  module Command
    class Base
      module Lifecycle
        extend ActiveSupport::Concern

        class_methods do
          #
          # The entry point of a command
          # This is registered with Discordrb and is called as part of an interaction lifecycle
          #
          # @note This method will gracefully handle 99% of the errors automatically.
          # I recommend avoiding manually handling exceptions in a command's lifecycle so this system can handle it. But the choice is up to you
          #
          # @!visibility private
          #
          def event_hook(event)
            event = ESM::Discord::Event::ApplicationCommand.new(event)
            event.on_execution(self)
          end
        end

        DISCORD_ACCESS_CHECKS = %i[
          check_for_dev_only!
          check_for_registered!
          check_for_text_only!
          check_for_dm_only!
          check_for_player_mode!
          check_for_permissions!
        ].freeze

        WEBSITE_ACCESS_CHECKS = %i[
          check_for_dev_only!
          check_for_registered!
          check_for_player_mode!
          check_for_permissions!
        ].freeze

        #
        # Called internally by #execute, this method handles when a command has been executed on Discord.
        #
        def from_discord!
          run_lifecycle!(access_checks: DISCORD_ACCESS_CHECKS)
        end

        #
        # Called when a command has been invoked through the website's RPC layer.
        # Skips text/DM-channel checks since the website has no channel context.
        #
        def from_website!
          run_lifecycle!(access_checks: WEBSITE_ACCESS_CHECKS)
        end

        # @param request [ESM::Request] The request to build this command with
        # @note Don't load `target_user` from the request. If the arguments contain a target, it will handle it
        def from_request(request)
          @request = request

          arguments.merge!(request.command_arguments.symbolize_keys) if request.command_arguments.present?

          timers.time!(:from_request) do
            load_v1_code! if v1_code_needed? # V1

            if @request.accepted
              on_request_accepted
            else
              # Reset the cooldown since the request was declined.
              current_cooldown.reset! if current_cooldown.present?

              on_request_declined
            end
          end
        end

        def on_execute
        end

        def on_request_accepted
        end

        def on_request_declined
        end

        private

        def run_lifecycle!(access_checks:)
          # Check for these BEFORE validating the arguments so even if an argument was invalid, it doesn't matter since these take priority
          timers.time!(:access_checks) do
            access_checks.each { |check| public_send(check) }
          end

          # Now ensure the user hasn't smoked too much lead
          timers.time!(:argument_validation) do
            arguments.validate!
          end

          info!(to_h)

          timers.time!(:before_execute) do
            check_for_nil_target_community! unless skipped_actions.nil_target_community?
            check_for_nil_target_server! unless skipped_actions.nil_target_server?
            check_for_nil_target_user! unless skipped_actions.nil_target_user?
            check_for_connected_server! unless skipped_actions.connected_server?
            check_for_cooldown! unless skipped_actions.cooldown?
            check_for_different_community! unless skipped_actions.different_community?
          end

          result = nil
          timers.time!(:on_execute) do
            load_v1_code! if v1_code_needed? # V1

            result = on_execute
          end

          timers.time!(:after_execute) do
            # Update the cooldown after the command has ran just in case there are issues
            create_or_update_cooldown unless skipped_actions.cooldown?
          end

          result
        end
      end
    end
  end
end
All opinions represented herein are my own
- © 2024 - 2026 itsthedevman
- build 43aa0d7