Packages
ex_gram
0.60.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/adapter/tesla.ex
if Code.ensure_loaded?(Tesla) do
defmodule ExGram.Adapter.Tesla do
@moduledoc """
HTTP adapter that uses [Tesla](https://hexdocs.pm/tesla) for Telegram Bot API requests.
This adapter supports multiple HTTP clients (Hackney, Finch, Gun, Mint, Httpc, Ibrowse)
and provides configurable middleware support.
See `ExGram.Adapter` behaviour for more details.
"""
@behaviour ExGram.Adapter
alias Tesla.Adapter.Hackney
require Logger
@base_url "https://api.telegram.org"
def middlewares do
[
{Tesla.Middleware.BaseUrl, ExGram.Config.get(:ex_gram, :base_url, @base_url)},
{Tesla.Middleware.Headers, [{"Content-Type", "application/json"}]},
{Tesla.Middleware.Logger, ExGram.Config.get(:ex_gram, Tesla.Middleware.Logger, level: :info)},
{
Tesla.Middleware.JSON,
decode: &__MODULE__.custom_decode/1, encode: &__MODULE__.custom_encode/1
}
]
end
def custom_encode(x), do: ExGram.Encoder.encode(x)
def custom_decode(x), do: ExGram.Encoder.decode(x, keys: :atoms)
@impl ExGram.Adapter
def request(verb, path, body, _opts) do
body = encode_body(body)
verb |> do_request(path, body) |> handle_result()
end
defp new do
Tesla.client(middlewares() ++ custom_middlewares(), http_adapter())
end
defp do_request(:get, path, body) do
do_request(:post, path, body)
end
defp do_request(:post, path, body) do
Tesla.post(new(), path, body, opts: opts())
end
defp handle_result({:ok, %{body: %{ok: true, result: body}, status: status}}) when status in 200..299 do
{:ok, body}
end
defp handle_result({:ok, %{body: %{ok: false, description: description, error_code: error_code} = body}}) do
parameters = maybe_error_parameters(body)
error = %ExGram.Error{code: error_code, message: description, metadata: %{parameters: parameters}}
{:error, error}
end
defp handle_result({:ok, %{body: body}}) do
{:error, %ExGram.Error{code: :response_status_not_match, message: ExGram.Adapter.encode(body)}}
end
defp handle_result({:error, reason}) do
{:error, %ExGram.Error{code: reason}}
end
defp maybe_error_parameters(%{parameters: parameters}) do
case ExGram.Cast.cast(parameters, {:array, ExGram.Model.ResponseParameters}) do
{:ok, parameters} -> parameters
_ -> []
end
end
defp maybe_error_parameters(_), do: []
defp encode_body(body) when is_map(body) do
Map.new(body, fn {key, value} -> {key, ExGram.Adapter.encode(value)} end)
end
defp encode_body({:multipart, parts}) do
mp = Tesla.Multipart.add_content_type_param(Tesla.Multipart.new(), "charset=utf-8")
Enum.reduce(parts, mp, &add_multipart_part/2)
end
defp add_multipart_part({:file, name, path}, mp) do
Tesla.Multipart.add_file(mp, path, name: name)
end
defp add_multipart_part({:file_content, name, content, filename}, mp) do
Tesla.Multipart.add_file_content(mp, content, filename, name: name)
end
defp add_multipart_part({name, value}, mp) do
Tesla.Multipart.add_field(mp, name, value)
end
defp http_adapter, do: Application.get_env(:tesla, :adapter) || Hackney
defp opts, do: [adapter: adapter_opts()]
defp adapter_opts do
adapter_module =
case http_adapter() do
module when is_atom(module) -> module
{module, _opts} -> module
_ -> nil
end
adapter_opts(adapter_module)
end
defp adapter_opts(Hackney) do
[connect_timeout: 20_000, timeout: 60_000, recv_timeout: 60_000]
end
defp adapter_opts(Tesla.Adapter.Finch) do
[pool_timeout: 20_000, receive_timeout: 60_000]
end
defp adapter_opts(Tesla.Adapter.Gun) do
[connect_timeout: 20_000, timeout: 60_000]
end
defp adapter_opts(Tesla.Adapter.Mint) do
[timeout: 60_000]
end
defp adapter_opts(Tesla.Adapter.Httpc) do
[connect_timeout: 20_000, timeout: 60_000]
end
defp adapter_opts(Tesla.Adapter.Ibrowse) do
[connect_timeout: 20_000, timeout: 60_000]
end
defp adapter_opts(_), do: []
defp format_middleware({m, f, a}) do
case apply(m, f, a) do
{_, _} = middleware -> {:ok, middleware}
_ -> :error
end
end
defp format_middleware({_, _} = mf), do: {:ok, mf}
defp format_middleware(_), do: :error
defp custom_middlewares do
middlewares = Application.get_env(:ex_gram, __MODULE__, [])[:middlewares] || []
middlewares
|> Enum.reduce([], fn elem, acc ->
case format_middleware(elem) do
{:ok, middleware} ->
[middleware | acc]
:error ->
Logger.warning("Discarded, element is not a middleware: #{inspect(elem)}")
acc
end
end)
|> Enum.reverse()
end
end
end