guides/middlewares.md

# Middlewares

Middlewares are functions that run **before** your handler processes an update. They're perfect for authentication, logging, rate limiting, or enriching the context with additional data.

## What Are Middlewares?

A middleware is either:
- A module implementing the `ExGram.Middleware` behaviour
- A function with signature: `(Cnt.t(), opts :: any()) -> Cnt.t()`

Middlewares receive the context, modify it, and return it. The modified context is passed to the next middleware or handler.

## Using Built-in Middlewares

### `ExGram.Middleware.IgnoreUsername`

This middleware strips the bot's username from commands, allowing both `/start` and `/start@yourbot` to work identically.

```elixir
defmodule MyBot.Bot do
  use ExGram.Bot, name: :my_bot

  # Add middleware at module level
  middleware(ExGram.Middleware.IgnoreUsername)

  def handle({:command, "start", _}, context) do
    # Handles both /start and /start@my_bot
    answer(context, "Welcome!")
  end
end
```

**Why use this?**
In group chats, users often mention the bot explicitly (`/command@botname`). This middleware normalizes commands.

## Creating Custom Middlewares

### Function-based Middleware

The simplest approach is a function:

```elixir
defmodule MyBot.Bot do
  use ExGram.Bot, name: :my_bot

  # Define middleware function
  middleware(&log_updates/2)

  # Middleware function
  def log_updates(context, _opts) do
    user = extract_user(context)
    update_type = extract_update_type(context)
    
    Logger.info("Update from #{user.id}: #{update_type}")
    
    context  # Return context
  end

  def handle({:command, "start", _}, context) do
    answer(context, "Hello!")
  end
end
```

### Module-based Middleware

For more complex logic, implement the `ExGram.Middleware` behaviour:

```elixir
defmodule MyBot.AuthMiddleware do
  @behaviour ExGram.Middleware

  def call(context, opts) do
    user = ExGram.Dsl.extract_user(context)
    
    if authorized?(user.id, opts) do
      # User is authorized, continue
      context
    else
      # User is not authorized, halt processing
      ExGram.Dsl.answer(context, "⛔ Access denied")
      |> Map.put(:halted, true)
    end
  end

  defp authorized?(user_id, opts) do
    allowed_users = Keyword.get(opts, :allowed_users, [])
    user_id in allowed_users
  end
end
```

Use it in your bot:

```elixir
defmodule MyBot.Bot do
  use ExGram.Bot, name: :my_bot

  # Pass options to middleware
  middleware({MyBot.AuthMiddleware, [allowed_users: [123456, 789012]]})

  def handle({:command, "admin", _}, context) do
    # Only authorized users reach here
    answer(context, "Admin panel: ...")
  end
end
```

## Halting the Middleware Chain

Set `halted: true` to stop processing:

```elixir
def call(context, _opts) do
  if rate_limited?(context) do
    context
    |> ExGram.Dsl.answer("⏱️ Please wait before sending another command")
    |> Map.put(:halted, true)
  else
    context
  end
end
```

When `halted: true`, no further middlewares or handlers execute.

### `middleware_halted` vs `halted`

- `middleware_halted: true` - Stop middleware chain, but run handler
- `halted: true` - Stop everything (middlewares + handler)

## Enriching Context with Extra Data

Add custom data to `context.extra` for use in handlers:

```elixir
defmodule MyBot.UserDataMiddleware do
  @behaviour ExGram.Middleware

  def call(context, _opts) do
    user = ExGram.Dsl.extract_user(context)
    user_data = fetch_user_from_database(user.id)
    
    # Add to context.extra
    ExGram.Cnt.add_extra(context, %{
      user_role: user_data.role,
      user_premium: user_data.premium?,
      user_lang: user_data.language
    })
  end

  defp fetch_user_from_database(user_id) do
    # Database lookup
    %{role: :user, premium?: false, language: "en"}
  end
end
```

Use in handlers:

```elixir
def handle({:command, "premium_feature", _}, context) do
  if context.extra[:user_premium] do
    answer(context, "✨ Premium feature unlocked!")
  else
    answer(context, "⭐ This feature requires premium")
  end
end
```

## Command and Regex Macros

The `command/2` and `regex/2` macros are actually middleware builders:

```elixir
defmodule MyBot.Bot do
  use ExGram.Bot, name: :my_bot

  # These register commands and patterns
  command("start", description: "Start the bot")
  command("help", description: "Get help")
  
  regex(:email, ~r/\b[A-Za-z0-9._%+-]+@/)
  regex(:url, ~r|https?://[^\s]+|)

  # Handlers match against registered commands/patterns
  def handle({:command, "start", _}, context), do: answer(context, "Hi!")
  def handle({:regex, :email, _}, context), do: answer(context, "Found an email!")
end
```

With `setup_commands: true`, commands are automatically registered with Telegram's BotFather menu.

## Multiple Middlewares

Middlewares execute in the order they're defined:

```elixir
defmodule MyBot.Bot do
  use ExGram.Bot, name: :my_bot

  # Execution order: 1 → 2 → 3 → handler
  middleware(&log_middleware/2)           # 1
  middleware(MyBot.AuthMiddleware)        # 2
  middleware(ExGram.Middleware.IgnoreUsername)  # 3

  def handle({:command, "start", _}, context) do
    answer(context, "Hello!")
  end
end
```

If middleware 2 halts, middleware 3 and the handler don't run.

## Common Middleware Patterns

### Rate Limiting

```elixir
defmodule MyBot.RateLimitMiddleware do
  @behaviour ExGram.Middleware

  def call(context, opts) do
    user_id = ExGram.Dsl.extract_user(context).id
    limit = Keyword.get(opts, :per_minute, 10)
    
    case check_rate_limit(user_id, limit) do
      :ok ->
        context
      
      {:error, retry_after} ->
        context
        |> ExGram.Dsl.answer("⏱️ Rate limited. Try again in #{retry_after}s")
        |> Map.put(:halted, true)
    end
  end

  defp check_rate_limit(user_id, limit) do
    # Check Redis/ETS for request count
    :ok
  end
end
```

### Language Detection

```elixir
defmodule MyBot.LanguageMiddleware do
  @behaviour ExGram.Middleware

  def call(context, _opts) do
    user = ExGram.Dsl.extract_user(context)
    
    # Detect from user's Telegram language or database
    lang = user.language_code || "en"
    
    ExGram.Cnt.add_extra(context, %{language: lang})
  end
end
```

### Command Analytics

```elixir
def analytics_middleware(context, _opts) do
  case ExGram.Dsl.extract_update_type(context) do
    :message ->
      if command = extract_command(context) do
        track_command(command)
      end
    _ -> :ok
  end
  
  context
end

defp extract_command(%{update: %{message: %{text: "/" <> cmd}}}), do: cmd
defp extract_command(_), do: nil
```

## Testing Middlewares

Test middlewares by creating a context and calling them:

```elixir
defmodule MyBot.AuthMiddlewareTest do
  use ExUnit.Case
  
  test "allows authorized users" do
    context = build_context(user_id: 123456)
    opts = [allowed_users: [123456]]
    
    result = MyBot.AuthMiddleware.call(context, opts)
    
    refute result.halted
  end
  
  test "blocks unauthorized users" do
    context = build_context(user_id: 999999)
    opts = [allowed_users: [123456]]
    
    result = MyBot.AuthMiddleware.call(context, opts)
    
    assert result.halted
  end
end
```

## Next Steps

- [Handling Updates](handling-updates.md) - Understanding handlers
- [Sending Messages](sending-messages.md) - DSL for building responses
- [Low-Level API](low-level-api.md) - Direct API calls for complex scenarios
- [Multiple Bots](multiple-bots.md) - Running multiple bots