Current section
Files
Jump to
Current section
Files
lib/api.ex
defmodule TelegramEx.API do
@moduledoc """
HTTP wrapper around the Telegram Bot API.
Most application code should use builder modules instead of calling this
module directly. See [Messages and Media](messages-and-media.md).
"""
require Logger
alias TelegramEx.Types
@type updates :: Types.updates()
@type request_context :: %{
required(:chat_id) => integer(),
required(:token) => String.t(),
required(:method) => String.t(),
required(:payload) => map(),
required(:format) => :json | :multipart,
optional(:message_thread_id) => integer()
}
defp client do
Application.get_env(:telegram_ex, :req_client) ||
Req.new()
|> ReqProxy.attach()
|> tap(&Application.put_env(:telegram_ex, :req_client, &1))
end
@doc """
Fetches updates from Telegram using long polling.
This function is called internally by `TelegramEx.Server` to retrieve
new messages and callbacks.
"""
@spec get_updates(String.t(), integer()) :: {:ok, updates()} | {:error, any()}
def get_updates(token, offset) do
case client()
|> Req.get(
url: "https://api.telegram.org/bot#{token}/getUpdates",
params: [offset: offset]
) do
{:ok, %{status: 200, body: %{"ok" => true, "result" => updates}}} ->
{:ok, updates}
{:ok, %{body: %{"description" => reason} = body}} ->
Logger.error(reason)
{:error, TelegramEx.Error.from_body(body)}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Sends a request to the Telegram Bot API.
Expects a builder context with `:chat_id`, `:token`, `:method`, `:payload`,
and `:format`.
"""
@spec request(request_context()) :: :ok | {:error, term()}
def request(%{chat_id: chat_id, token: token, method: method, payload: payload} = ctx) do
payload = Map.put(payload, :chat_id, chat_id)
payload =
if Map.get(ctx, :message_thread_id),
do: Map.put(payload, :message_thread_id, ctx.message_thread_id),
else: payload
case ctx[:format] do
:json ->
client()
|> Req.post(url: "https://api.telegram.org/bot#{token}/#{method}", json: payload)
|> handle_response()
:multipart ->
client()
|> Req.post(
url: "https://api.telegram.org/bot#{token}/#{method}",
form_multipart: payload
)
|> handle_response()
_ ->
{:error, :invalid_format}
end
end
@doc """
Answers a callback query from an inline keyboard button.
This function should be called after handling a callback query to
acknowledge it and optionally show an alert or notification to the user.
Most code should call `TelegramEx.Builder.Message.answer_callback_query/2`
in a builder pipeline.
"""
@spec answer_callback_query(String.t(), Types.CallbackQuery.t()) :: :ok | {:error, any()}
def answer_callback_query(token, %{id: id}) do
client()
|> Req.post(
url: "https://api.telegram.org/bot#{token}/answerCallbackQuery",
json: %{callback_query_id: id}
)
|> handle_response()
end
def set_my_commands(commands, token) when is_list(commands) do
client()
|> Req.post(
url: "https://api.telegram.org/bot#{token}/setMyCommands",
json: %{commands: commands}
)
|> handle_response()
end
defp handle_response({:ok, %{status: 200}}) do
:ok
end
defp handle_response({:ok, %{body: %{"description" => reason} = body}}) do
Logger.error(reason)
{:error, TelegramEx.Error.from_body(body)}
end
defp handle_response({:ok, response}) do
Logger.error("Unknown error: #{inspect(response)}")
{:error, :unknown_error}
end
defp handle_response({:error, reason}) do
{:error, reason}
end
end