ExGram.Router (ex_gram_router v0.1.0)

Copy Markdown View Source

A declarative routing DSL for ExGram bots.

ExGram.Router provides a scope/filter/handle DSL that replaces writing individual handle/2 clause pattern-matches by hand. Everything is a filter: built-in filters cover the common update types (commands, text, callback queries, etc.) and custom filters can encode arbitrary runtime predicates (e.g., checking conversation state, user roles).

Usage

defmodule MyBot do
  use ExGram.Bot, name: :my_bot
  use ExGram.Router

  # Alias custom filters for convenience (optional)
  alias_filter MyApp.Filters.State, as: :state

  # /start command
  scope do
    filter :command, :start
    handle &MyBot.Handlers.start/1
  end

  # Registration flow (state-gated)
  scope do
    filter :state, :registration

    scope do
      filter :text
      filter :state, :get_name
      handle &MyBot.Handlers.get_name/2
    end

    scope do
      filter :text
      filter :state, :get_email
      handle &MyBot.Handlers.get_email/1
    end

    # Admin sub-scope within registration
    scope do
      filter MyBot.Filters.AdminCheck
      filter :command, :admin
      handle &MyBot.Handlers.admin_cmd/2
    end
  end

  # Fallback: no filters = matches everything
  scope do
    handle &MyBot.Handlers.fallback/1
  end
end

Scope rules

A scope can contain:

  • Zero or more filter declarations (evaluated in order with AND logic)
  • Either a handle (leaf scope) OR nested scope blocks (branch scope), not both
  • A scope with no filters acts as a pass-through (all updates reach it)

Built-in filter aliases

The following aliases are available without alias_filter:

AliasModuleExample usage
:commandExGram.Router.Filters.Commandfilter :command, :start
:textExGram.Router.Filters.Textfilter :text
:callback_queryExGram.Router.Filters.CallbackQueryfilter :callback_query, "action_a"
:regexExGram.Router.Filters.Regexfilter :regex, :email
:messageExGram.Router.Filters.Messagefilter :message
:inline_queryExGram.Router.Filters.InlineQueryfilter :inline_query
:locationExGram.Router.Filters.Locationfilter :location
:animationExGram.Router.Filters.Animationfilter :animation
:audioExGram.Router.Filters.Audiofilter :audio
:contactExGram.Router.Filters.Contactfilter :contact
:documentExGram.Router.Filters.Documentfilter :document
:photoExGram.Router.Filters.Photofilter :photo
:pollExGram.Router.Filters.Pollfilter :poll
:stickerExGram.Router.Filters.Stickerfilter :sticker
:videoExGram.Router.Filters.Videofilter :video
:video_noteExGram.Router.Filters.VideoNotefilter :video_note
:voiceExGram.Router.Filters.Voicefilter :voice

use ExGram.Router options

  • aliases: [atom: Module, ...] - Additional filter aliases to merge with the builtins. Each key must not conflict with an existing builtin alias.
  • exclude_aliases: [:atom, ...] - Builtin (or user-provided) aliases to remove from the final alias set. Useful when you want to prevent a builtin from being referenced.

Example:

use ExGram.Router,
  aliases: [state: MyApp.Filters.State],
  exclude_aliases: [:poll, :video_note]

Custom filters

Implement the ExGram.Router.Filter behaviour:

defmodule MyApp.Filters.AdminOnly do
  @behaviour ExGram.Router.Filter

  @impl ExGram.Router.Filter
  def call(_update_info, context, _opts) do
    {:ok, user} = ExGram.Dsl.extract_user(context)
    user.id in Application.fetch_env!(:my_app, :admin_ids)
  end
end

Handler arities

  • 1-arity &MyMod.fun/1 — receives only the ExGram.Cnt.t() context
  • 2-arity &MyMod.fun/2 — receives (update_info, context), where update_info is the parsed update tuple (e.g. {:command, :start, msg})

alias_filter syntax

alias_filter SomeFilterModule, as: :my_alias

After this, filter :my_alias, opts resolves to SomeFilterModule.call(update_info, context, opts).