Packages
ex_gram
0.66.0
0.67.0
0.66.0
0.65.0
0.64.0
0.63.0
0.62.0
0.61.0
0.60.0
0.58.0
0.57.0
0.56.1
0.56.0
0.55.1
0.55.0
0.54.0
0.53.0
0.52.2
0.52.1
0.52.0
0.51.1
0.51.0
0.50.2
0.50.1
0.50.0
0.41.0
0.40.0
0.34.0
0.33.0
0.32.0
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.1
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.1
0.8.0
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.0
0.5.0-rc6
0.5.0-rc5
0.5.0-rc4
0.5.0-rc3
0.5.0-rc2
Telegram Bot API low level and framework
Current section
Files
Jump to
Current section
Files
lib/ex_gram/plug.ex
if Code.ensure_loaded?(Plug) do
defmodule ExGram.Plug do
@moduledoc """
A Plug to handle incoming Telegram updates in webhook mode. It checks for the secret
token (if configured) and processes the update accordingly.
This plug is used with `ExGram.Updates.Webhook` to receive updates from Telegram.
See the [Polling and Webhooks](polling-and-webhooks.md) guide for setup instructions.
"""
@behaviour Plug
import Plug.Conn
alias ExGram.Model.Update
alias Plug.Conn
require Logger
@impl true
def init(opts) do
path = opts[:path] || ExGram.Config.get(:ex_gram, :webhook)[:path] || "/telegram"
path_parts = String.split(path, "/", trim: true)
[path_parts: path_parts]
end
@impl true
def call(%Conn{method: "POST"} = conn, opts) do
if path_match?(conn, opts) do
handle_update(conn)
else
conn
end
end
def call(conn, _), do: conn
defp path_match?(%Conn{path_info: path_info}, opts) do
path_parts = Keyword.fetch!(opts, :path_parts)
path_parts_length = length(path_parts)
# path can be /a/b/c, we should check if all the first parts match,
# and there's only one extra part (the token hash)
Enum.take(path_info, path_parts_length) == path_parts and
length(path_info) == path_parts_length + 1
end
defp handle_update(conn) do
{:ok, body, conn} = Conn.read_body(conn)
{status, message} =
case conn
|> get_req_header("x-telegram-bot-api-secret-token")
|> check_secret_token() do
:ok -> handle_update(conn, ExGram.Encoder.decode(body, keys: :atoms))
{:error, error} -> {400, %{error: error}}
end
conn
|> put_resp_content_type("application/json")
|> send_resp(status, ExGram.Encoder.encode!(message))
|> halt()
end
defp handle_update(conn, {:ok, update}) do
token_hash = token_hash(conn.path_info)
case ExGram.Cast.cast(update, Update) do
{:ok, update} ->
ExGram.Updates.Webhook.update(update, token_hash)
{200, %{ok: true}}
{:error, error} ->
Logger.error("Failed to cast update: #{Exception.message(error)}")
{400, %{ok: false, error: "Body is not a valid Telegram update"}}
end
end
defp handle_update(_conn, {:error, error}), do: {400, %{error: Exception.message(error)}}
defp check_secret_token([]) do
Logger.debug(
"The secret token is not configured in webhook mode. For security purposes, it is recommended to set one."
)
:ok
end
defp check_secret_token([secret_token]) do
config_token = ExGram.Config.get(:ex_gram, :webhook)[:secret_token]
case {secret_token, config_token} do
{_secret_token, nil} ->
message =
"The secret token exists in the request header but is not configured in the application's settings."
Logger.error(message)
{:error, message}
{secret_token, config_token} when secret_token != config_token ->
message =
"The secret token in the request header does not match the one configured in the webhook mode."
Logger.error(message)
{:error, message}
{secret_token, config_token} when secret_token == config_token ->
:ok
end
end
defp token_hash([path]) when is_binary(path), do: path
defp token_hash([_ | tl]), do: token_hash(tl)
end
end