Current section

Files

Jump to
telega src telega@client.erl
Raw

src/telega@client.erl

-module(telega@client).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/telega/client.gleam").
-export([set_fetch_client/2, set_max_retry_attempts/2, set_tg_api_url/2, get_token/1, default_request_queue_config/0, set_request_queue/2, shutdown/1, get_queue_length/1, is_queue_overheated/1, get_api_url/1, new_post_request/3, new_get_request/3, fetch/2, fetch_with_rule/3, new/1, new_with_queue/1]).
-export_type([telegram_client/0, request_queue_config/0, request_queue_rule/0, telegram_api_request/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Module provides a simple interface to the Telegram Bot API and uses `httpc` as a default HTTP client.\n"
" If you want to use `telega` as a Telegram client, you can use only this module.\n"
"\n"
" ```gleam\n"
" import telega/client\n"
" import telega/api\n"
"\n"
" fn main() {\n"
" ...\n"
" let response = client.new(token) |> api.send_message(client, send_message_parameters)\n"
" ...\n"
" }\n"
" ```\n"
).
-opaque telegram_client() :: {telegram_client,
binary(),
integer(),
binary(),
fun((gleam@http@request:request(binary())) -> {ok,
gleam@http@response:response(binary())} |
{error, telega@error:telega_error()}),
gleam@option:option(telega@internal@request_queue:request_queue())}.
-type request_queue_config() :: {request_queue_config,
list(request_queue_rule()),
gleam@option:option(integer()),
gleam@option:option(integer()),
integer(),
integer()}.
-type request_queue_rule() :: {request_queue_rule,
binary(),
integer(),
integer(),
integer()}.
-opaque telegram_api_request() :: {telegram_api_post_request,
binary(),
binary()} |
{telegram_api_get_request,
binary(),
gleam@option:option(list({binary(), binary()}))}.
-file("src/telega/client.gleam", 94).
?DOC(" Set the HTTP client to use.\n").
-spec set_fetch_client(
telegram_client(),
fun((gleam@http@request:request(binary())) -> {ok,
gleam@http@response:response(binary())} |
{error, telega@error:telega_error()})
) -> telegram_client().
set_fetch_client(Client, Fetch_client) ->
{telegram_client,
erlang:element(2, Client),
erlang:element(3, Client),
erlang:element(4, Client),
Fetch_client,
erlang:element(6, Client)}.
-file("src/telega/client.gleam", 103).
?DOC(" Set the maximum number of times to retry sending a API message.\n").
-spec set_max_retry_attempts(telegram_client(), integer()) -> telegram_client().
set_max_retry_attempts(Client, Max_retry_attempts) ->
{telegram_client,
erlang:element(2, Client),
Max_retry_attempts,
erlang:element(4, Client),
erlang:element(5, Client),
erlang:element(6, Client)}.
-file("src/telega/client.gleam", 111).
?DOC(" Set the Telegram Bot API URL.\n").
-spec set_tg_api_url(telegram_client(), binary()) -> telegram_client().
set_tg_api_url(Client, Tg_api_url) ->
{telegram_client,
erlang:element(2, Client),
erlang:element(3, Client),
Tg_api_url,
erlang:element(5, Client),
erlang:element(6, Client)}.
-file("src/telega/client.gleam", 119).
?DOC(" Get the bot token from the client\n").
-spec get_token(telegram_client()) -> binary().
get_token(Client) ->
erlang:element(2, Client).
-file("src/telega/client.gleam", 150).
-spec default_request_queue_config() -> request_queue_config().
default_request_queue_config() ->
Default_config = telega@internal@request_queue:default_config(),
Rules = gleam@list:map(
erlang:element(2, Default_config),
fun(Rule) ->
{request_queue_rule,
erlang:element(2, Rule),
erlang:element(3, Rule),
erlang:element(4, Rule),
erlang:element(5, Rule)}
end
),
{request_queue_config,
Rules,
erlang:element(3, Default_config),
erlang:element(4, Default_config),
erlang:element(5, Default_config),
erlang:element(6, Default_config)}.
-file("src/telega/client.gleam", 225).
?DOC(
" Enable request queue with custom configuration for rate limiting\n"
"\n"
" The request queue helps prevent hitting Telegram's rate limits by:\n"
" - Queuing requests when limits are reached\n"
" - Automatically retrying failed requests with exponential backoff\n"
" - Supporting different rate limits for different types of requests\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import telega/client\n"
"\n"
" let config = client.RequestQueueConfig(\n"
" rules: [\n"
" // Default rule for most requests\n"
" client.RequestQueueRule(\n"
" id: \"default\",\n"
" rate: 30, // 30 requests\n"
" limit: 1000, // per 1 second\n"
" priority: 5,\n"
" ),\n"
" // Slower rate for sending messages\n"
" client.RequestQueueRule(\n"
" id: \"send_message\",\n"
" rate: 1, // 1 request\n"
" limit: 1000, // per 1 second\n"
" priority: 10,\n"
" ),\n"
" // Higher priority for important requests\n"
" client.RequestQueueRule(\n"
" id: \"important\",\n"
" rate: 5,\n"
" limit: 1000,\n"
" priority: 1, // Lower number = higher priority\n"
" ),\n"
" ],\n"
" overall_rate: Some(30), // Global limit across all rules\n"
" overall_limit: Some(100), // Max concurrent requests\n"
" retry_delay: 1000, // Retry after 1 second\n"
" max_retries: 3,\n"
" )\n"
"\n"
" let assert Ok(client) =\n"
" client.new(token)\n"
" |> client.set_request_queue(config)\n"
"\n"
" // Use specific rule for rate-limited operations\n"
" client.fetch_with_rule(request, client, \"send_message\")\n"
"\n"
" // Check queue status\n"
" let queue_length = client.get_queue_length(client)\n"
" let is_busy = client.is_queue_overheated(client)\n"
" ```\n"
).
-spec set_request_queue(telegram_client(), request_queue_config()) -> {ok,
telegram_client()} |
{error, telega@error:telega_error()}.
set_request_queue(Client, Config) ->
case erlang:element(6, Client) of
{some, Queue} ->
telega@internal@request_queue:shutdown(Queue);
none ->
nil
end,
gleam@result:'try'(
begin
_pipe = telega@internal@request_queue:start(
{queue_config,
gleam@list:map(
erlang:element(2, Config),
fun(Rule) ->
{rule,
erlang:element(2, Rule),
erlang:element(3, Rule),
erlang:element(4, Rule),
erlang:element(5, Rule)}
end
),
erlang:element(3, Config),
erlang:element(4, Config),
erlang:element(5, Config),
erlang:element(6, Config)}
),
gleam@result:map_error(
_pipe,
fun(_) ->
{fetch_error, <<"Failed to start request queue"/utf8>>}
end
)
end,
fun(Queue@1) ->
{ok,
{telegram_client,
erlang:element(2, Client),
erlang:element(3, Client),
erlang:element(4, Client),
erlang:element(5, Client),
{some, Queue@1}}}
end
).
-file("src/telega/client.gleam", 260).
?DOC(
" Shutdown the client and its request queue\n"
"\n"
" Only recommended if request queue is enabled.\n"
).
-spec shutdown(telegram_client()) -> nil.
shutdown(Client) ->
case erlang:element(6, Client) of
{some, Queue} ->
telega@internal@request_queue:shutdown(Queue);
none ->
nil
end.
-file("src/telega/client.gleam", 270).
?DOC(
" Get the total number of requests waiting in the queue\n"
"\n"
" Returns 0 if no queue is configured\n"
).
-spec get_queue_length(telegram_client()) -> integer().
get_queue_length(Client) ->
case erlang:element(6, Client) of
{some, Queue} ->
telega@internal@request_queue:total_length(Queue);
none ->
0
end.
-file("src/telega/client.gleam", 280).
?DOC(
" Check if the queue is overheated (any rule is at its rate limit)\n"
"\n"
" Returns False if no queue is configured\n"
).
-spec is_queue_overheated(telegram_client()) -> boolean().
is_queue_overheated(Client) ->
case erlang:element(6, Client) of
{some, Queue} ->
telega@internal@request_queue:is_overheated(Queue);
none ->
false
end.
-file("src/telega/client.gleam", 305).
-spec send_request(telegram_client(), gleam@http@request:request(binary())) -> {ok,
gleam@http@response:response(binary())} |
{error, telega@error:telega_error()}.
send_request(Client, Api_request) ->
(erlang:element(5, Client))(Api_request).
-file("src/telega/client.gleam", 365).
-spec set_query(
gleam@http@request:request(UFR),
gleam@option:option(list({binary(), binary()}))
) -> gleam@http@request:request(UFR).
set_query(Api_request, Query) ->
case Query of
none ->
Api_request;
{some, Query@1} ->
gleam@http@request:set_query(Api_request, Query@1)
end.
-file("src/telega/client.gleam", 342).
-spec api_to_request(telegram_api_request()) -> {ok,
gleam@http@request:request(binary())} |
{error, telega@error:telega_error()}.
api_to_request(Api_request) ->
_pipe@7 = case Api_request of
{telegram_api_get_request, Url, Query} ->
_pipe = gleam@http@request:to(Url),
gleam@result:map(_pipe, fun(Req) -> _pipe@1 = Req,
_pipe@2 = gleam@http@request:set_method(_pipe@1, get),
set_query(_pipe@2, Query) end);
{telegram_api_post_request, Url@1, Body} ->
_pipe@3 = gleam@http@request:to(Url@1),
gleam@result:map(_pipe@3, fun(Req@1) -> _pipe@4 = Req@1,
_pipe@5 = gleam@http@request:set_body(_pipe@4, Body),
_pipe@6 = gleam@http@request:set_method(_pipe@5, post),
gleam@http@request:set_header(
_pipe@6,
<<"Content-Type"/utf8>>,
<<"application/json"/utf8>>
) end)
end,
gleam@result:map_error(_pipe@7, fun(_) -> api_to_request_convert_error end).
-file("src/telega/client.gleam", 372).
-spec get_api_url(telegram_client()) -> binary().
get_api_url(Client) ->
erlang:element(4, Client).
-file("src/telega/client.gleam", 397).
-spec fetch_httpc_adapter(gleam@http@request:request(binary())) -> {ok,
gleam@http@response:response(binary())} |
{error, telega@error:telega_error()}.
fetch_httpc_adapter(Req) ->
_pipe = gleam@httpc:send(Req),
gleam@result:map_error(
_pipe,
fun(Error) -> {fetch_error, gleam@string:inspect(Error)} end
).
-file("src/telega/client.gleam", 404).
-spec build_url(telegram_client(), binary()) -> binary().
build_url(Client, Path) ->
<<<<<<(erlang:element(4, Client))/binary,
(erlang:element(2, Client))/binary>>/binary,
"/"/utf8>>/binary,
Path/binary>>.
-file("src/telega/client.gleam", 381).
-spec new_post_request(telegram_client(), binary(), binary()) -> telegram_api_request().
new_post_request(Client, Path, Body) ->
{telegram_api_post_request, build_url(Client, Path), Body}.
-file("src/telega/client.gleam", 389).
-spec new_get_request(
telegram_client(),
binary(),
gleam@option:option(list({binary(), binary()}))
) -> telegram_api_request().
new_get_request(Client, Path, Query) ->
{telegram_api_get_request, build_url(Client, Path), Query}.
-file("src/telega/client.gleam", 312).
-spec send_with_retry(
telegram_client(),
gleam@http@request:request(binary()),
integer()
) -> {ok, gleam@http@response:response(binary())} |
{error, telega@error:telega_error()}.
send_with_retry(Client, Api_request, Retries) ->
Response = (erlang:element(5, Client))(Api_request),
case Retries of
0 ->
Response;
_ ->
case Response of
{ok, Response@1} ->
case erlang:element(2, Response@1) of
429 ->
gleam_erlang_ffi:sleep(1000),
send_with_retry(Client, Api_request, Retries - 1);
_ ->
{ok, Response@1}
end;
{error, _} ->
gleam_erlang_ffi:sleep(1000),
send_with_retry(Client, Api_request, Retries - 1)
end
end.
-file("src/telega/client.gleam", 79).
?DOC(
" Send a request to the Telegram Bot API.\n"
"\n"
" It uses `default` rule for rate limiting (if request [queue](#set_request_queue) is enabled).\n"
).
-spec fetch(telegram_api_request(), telegram_client()) -> {ok,
gleam@http@response:response(binary())} |
{error, telega@error:telega_error()}.
fetch(Api_request, Client) ->
gleam@result:'try'(
api_to_request(Api_request),
fun(Api_request@1) -> case erlang:element(6, Client) of
{some, Queue} ->
telega@internal@request_queue:execute(
Queue,
fun() -> send_request(Client, Api_request@1) end
);
none ->
send_with_retry(
Client,
Api_request@1,
erlang:element(3, Client)
)
end end
).
-file("src/telega/client.gleam", 287).
-spec fetch_with_rule(telegram_api_request(), telegram_client(), binary()) -> {ok,
gleam@http@response:response(binary())} |
{error, telega@error:telega_error()}.
fetch_with_rule(Api_request, Client, Rule_id) ->
gleam@result:'try'(
api_to_request(Api_request),
fun(Api_request@1) ->
Request_id = telega@internal@utils:random_string(32),
case erlang:element(6, Client) of
{some, Queue} ->
telega@internal@request_queue:execute_with_rule(
Queue,
Request_id,
Rule_id,
fun() -> send_request(Client, Api_request@1) end
);
none ->
send_with_retry(
Client,
Api_request@1,
erlang:element(3, Client)
)
end
end
).
-file("src/telega/client.gleam", 55).
?DOC(" Create a new Telegram client. It uses `httpc` as a default HTTP client.\n").
-spec new(binary()) -> telegram_client().
new(Token) ->
{telegram_client,
Token,
3,
<<"https://api.telegram.org/bot"/utf8>>,
fun fetch_httpc_adapter/1,
none}.
-file("src/telega/client.gleam", 69).
?DOC(
" Create a new Telegram client with default request queue configuration\n"
"\n"
" This is a convenience function that creates a client with sensible\n"
" default rate limiting settings for the Telegram Bot API.\n"
).
-spec new_with_queue(binary()) -> {ok, telegram_client()} |
{error, telega@error:telega_error()}.
new_with_queue(Token) ->
_pipe = new(Token),
set_request_queue(_pipe, default_request_queue_config()).