# Hooks

Hooks are a way to perform extra logic before and after a command is invoked. This is done using the concept of
an 'execution pipeline' that has several steps which are executed in order when the user calls a command
from within discord.

All the steps must complete successfully in order for a command invocation to be considered complete. If any one
of the hooks fails for any of the steps, the pipeline fails and an error will be thrown.

---

## Creating a Hook

Hooks are created using the `lightbulb.hook` decorator. They can be either synchronous or asynchronous functions - both
will work. When creating a hook, you must specify the step that it runs on, and you can optionally specify
whether the hook should be skipped if the pipeline has already failed.

By default, all hooks will always run no matter whether the pipeline has failed or not - the exception is the 
command invocation function, which will never be executed if any preceding hooks have failed. Pass 
`skip_when_failed=True` to the hook decorator to avoid execution of the hook if the pipeline has already failed.

To fail the pipeline, hooks can raise any exception. This will be caught and re-raised as an
`ExecutionPipelineFailedException` once any remaining hooks have been run.

Below is an example hook that checks if the person who invoked the command is me (thomm.o) and will fail otherwise.

```python
@lightbulb.hook(lightbulb.ExecutionSteps.CHECKS)
def fail_if_not_thommo(_: lightbulb.ExecutionPipeline, ctx: lightbulb.Context) -> None:
    if ctx.user.id != 215061635574792192:
        raise RuntimeError("only thomm.o can use this command")
```

As seen above, hooks must take at least 2 arguments - the execution pipeline being run, and the context that the
command was invoked under. Lightbulb will attempt to dependency-inject any further arguments.

---

## Adding Hooks to Commands

Adding hooks to commands is very simple - you just pass them to the `hooks` class parameter.

```python
class YourCommand(
    lightbulb.SlashCommand,
    ...,
    hooks=[fail_if_not_thommo]
):
    ...
```

When the command is invoked, hooks will be executed in the order they are defined, grouped by execution step.

---

## Step Order

Lightbulb provides a default step order - however, you can specify your own custom one when the `Client` is created
which will cause command execution to follow your defined steps instead. 

- The default order is exported as `lightbulb.DEFAULT_EXECUTION_STEP_ORDER` if you wish to augment it when creating your own.
- The provided execution steps are contained within `lightbulb.ExecutionSteps` if you wish to use them.

:::{warning}
Custom orders **must** contain the step `lightbulb.ExecutionStep.INVOKE` otherwise the command invocation callback
will never be run. Hooks also **cannot** be added for this step and will throw an error if you try to do so.
:::

---

## Custom Steps

Creating your own custom execution step is very simple and supported out-of-the-box by Lightbulb. All you have to
do is create an instance of `ExecutionStep` with a custom ID. Note that all step IDs must be unique - you cannot
define a new step with the same ID as an existing one.

```python
YOUR_STEP = lightbulb.ExecutionStep("YOUR_STEP_ID")
```

---

## Worked Custom Step Example

As a demo example for customizing the execution step order and defining a custom step, we are going to implement
automatic deferral of command responses before any other steps are run.

```python
import hikari
import lightbulb

# Define our custom execution step
AUTO_DEFER = lightbulb.ExecutionStep("AUTO_DEFER")

bot = hikari.GatewayBot(...)
client = lightbulb.client_from_app(
    bot,
    # Add our custom step to the step order. Our step will run first, followed by all the
    # default steps lightbulb would usually use.
    execution_step_order=[AUTO_DEFER, *lightbulb.DEFAULT_EXECUTION_STEP_ORDER]
)
bot.subscribe(hikari.StartingEvent, client.start)


# Define our hook to defer the command response
@lightbulb.hook(AUTO_DEFER)
async def auto_defer_command_response(_: lightbulb.ExecutionPipeline, ctx: lightbulb.Context) -> None:
    await ctx.defer()


@client.register
class AutoDeferredCommand(
    lightbulb.SlashCommand,
    name="auto-defer",
    description="auto defer test",
    # Add our hook to the command
    hooks=[auto_defer_command_response]
):
    @lightbulb.invoke
    async def invoke(self, ctx: lightbulb.Context) -> None:
        await ctx.respond("This command was auto-deferred!")


bot.run()
```

---

## Hook Inheritance

Sometimes, you may find yourself frequently adding the same hooks to all your commands. Lightbulb allows you to
pass some common hooks when creating your client which will then be applied to **all** commands registered
to the client upon invocation.

For example, if you want all your commands to only be usable by yourself then you could do the following:

```python
# Define the hook
@lightbulb.hook(lightbulb.ExecutionSteps.CHECKS)
async def only_me(_: lightbulb.ExecutionPipeline, ctx: lightbulb.Context) -> None:
    if ctx.user.id != YOUR_USER_ID:
        raise RuntimeError("you are not allowed to use the command")

# Add the hook to the client
client = lightbulb.client_from_app(..., hooks=[only_me])

# This command will only be usable by you!
@client.register
class YourCommand(...):
    ...
```

---

## Built-in Hooks

Lightbulb provides a few hooks for common use-cases so that you don't have to implement them yourself. They can be
found in the {obj}`lightbulb.prefab` subpackage. See the api reference for details
on what is available.

These include:

- Checks
- Cooldowns (using fixed-window or sliding-window algorithms)
- Max concurrency (limiting the number of command invocations happening at once)
