-module(telega@client). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/telega/client.gleam"). -export([new/2, default_request_queue_config/0, set_request_queue/2, new_with_queue/2, fetch/2, set_fetch_client/2, set_fetch_bits_client/2, get_fetch_bits_client/1, set_max_retry_attempts/2, set_tg_api_url/2, get_token/1, shutdown/1, get_queue_length/1, is_queue_overheated/1, fetch_with_rule/3, get_api_url/1, new_post_request/3, new_get_request/3]). -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.\n" " If you want to use `telega` as a Telegram client, you can use only this module.\n" "\n" " Use an adapter package like `telega_httpc` or `telega_hackney` to create a client,\n" " or provide your own `FetchClient` function.\n" "\n" " ```gleam\n" " import telega/client\n" " import telega/api\n" "\n" " fn main() {\n" " ...\n" " let response = client.new(token, my_fetch_adapter) |> 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(fun((gleam@http@request:request(bitstring())) -> {ok, gleam@http@response:response(bitstring())} | {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(), binary()} | {telegram_api_get_request, binary(), gleam@option:option(list({binary(), binary()})), binary()}. -file("src/telega/client.gleam", 63). ?DOC(" Create a new Telegram client with the given fetch client adapter.\n"). -spec new( binary(), fun((gleam@http@request:request(binary())) -> {ok, gleam@http@response:response(binary())} | {error, telega@error:telega_error()}) ) -> telegram_client(). new(Token, Fetch_client) -> {telegram_client, Token, 3, <<"https://api.telegram.org/bot"/utf8>>, Fetch_client, none, none}. -file("src/telega/client.gleam", 181). -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", 256). ?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(7, 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), erlang:element(6, Client), {some, Queue@1}}} end ). -file("src/telega/client.gleam", 81). ?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(), fun((gleam@http@request:request(binary())) -> {ok, gleam@http@response:response(binary())} | {error, telega@error:telega_error()}) ) -> {ok, telegram_client()} | {error, telega@error:telega_error()}. new_with_queue(Token, Fetch_client) -> _pipe = new(Token, Fetch_client), set_request_queue(_pipe, default_request_queue_config()). -file("src/telega/client.gleam", 380). -spec emit_api_retry(binary(), integer()) -> nil. emit_api_retry(Method, Attempt) -> telega@telemetry:execute( [<<"telega"/utf8>>, <<"api_call"/utf8>>, <<"retry"/utf8>>], [{<<"retry_after"/utf8>>, 1000}], [{<<"method"/utf8>>, {string_value, Method}}, {<<"attempt"/utf8>>, {int_value, Attempt}}] ). -file("src/telega/client.gleam", 391). -spec send_with_retry( telegram_client(), binary(), gleam@http@request:request(binary()), integer() ) -> {ok, gleam@http@response:response(binary())} | {error, telega@error:telega_error()}. send_with_retry(Client, Method, 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 -> emit_api_retry( Method, (erlang:element(3, Client) - Retries) + 1 ), gleam_erlang_ffi:sleep(1000), send_with_retry( Client, Method, Api_request, Retries - 1 ); _ -> {ok, Response@1} end; {error, _} -> emit_api_retry( Method, (erlang:element(3, Client) - Retries) + 1 ), gleam_erlang_ffi:sleep(1000), send_with_retry(Client, Method, Api_request, Retries - 1) end end. -file("src/telega/client.gleam", 339). -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", 347). ?DOC(" Wraps a request execution in `telega.api_call` start/stop/exception events.\n"). -spec fetch_with_telemetry( binary(), fun(() -> {ok, gleam@http@response:response(binary())} | {error, telega@error:telega_error()}) ) -> {ok, gleam@http@response:response(binary())} | {error, telega@error:telega_error()}. fetch_with_telemetry(Method, Run) -> Metadata = [{<<"method"/utf8>>, {string_value, Method}}], Started_at = erlang:monotonic_time(), telega@telemetry:execute( [<<"telega"/utf8>>, <<"api_call"/utf8>>, <<"start"/utf8>>], [{<<"system_time"/utf8>>, erlang:system_time()}], Metadata ), Result = Run(), Duration = erlang:monotonic_time() - Started_at, case Result of {ok, Response} -> telega@telemetry:execute( [<<"telega"/utf8>>, <<"api_call"/utf8>>, <<"stop"/utf8>>], [{<<"duration"/utf8>>, Duration}], [{<<"status"/utf8>>, {int_value, erlang:element(2, Response)}} | Metadata] ); {error, Error} -> telega@telemetry:execute( [<<"telega"/utf8>>, <<"api_call"/utf8>>, <<"exception"/utf8>>], [{<<"duration"/utf8>>, Duration}], [{<<"error"/utf8>>, {string_value, gleam@string:inspect(Error)}} | Metadata] ) end, Result. -file("src/telega/client.gleam", 447). -spec set_query( gleam@http@request:request(KKO), gleam@option:option(list({binary(), binary()})) ) -> gleam@http@request:request(KKO). 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", 424). -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", 92). ?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) -> Method = erlang:element(4, Api_request), gleam@result:'try'( api_to_request(Api_request), fun(Api_request@1) -> fetch_with_telemetry( Method, fun() -> case erlang:element(7, Client) of {some, Queue} -> telega@internal@request_queue:execute( Queue, fun() -> send_request(Client, Api_request@1) end ); none -> send_with_retry( Client, Method, Api_request@1, erlang:element(3, Client) ) end end ) end ). -file("src/telega/client.gleam", 110). ?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), erlang:element(7, Client)}. -file("src/telega/client.gleam", 119). ?DOC(" Set the binary HTTP client for file downloads.\n"). -spec set_fetch_bits_client( telegram_client(), fun((gleam@http@request:request(bitstring())) -> {ok, gleam@http@response:response(bitstring())} | {error, telega@error:telega_error()}) ) -> telegram_client(). set_fetch_bits_client(Client, Fetch_bits_client) -> {telegram_client, erlang:element(2, Client), erlang:element(3, Client), erlang:element(4, Client), erlang:element(5, Client), {some, Fetch_bits_client}, erlang:element(7, Client)}. -file("src/telega/client.gleam", 127). ?DOC(" Get the binary HTTP client, if configured.\n"). -spec get_fetch_bits_client(telegram_client()) -> gleam@option:option(fun((gleam@http@request:request(bitstring())) -> {ok, gleam@http@response:response(bitstring())} | {error, telega@error:telega_error()})). get_fetch_bits_client(Client) -> erlang:element(6, Client). -file("src/telega/client.gleam", 134). ?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), erlang:element(7, Client)}. -file("src/telega/client.gleam", 142). ?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), erlang:element(7, Client)}. -file("src/telega/client.gleam", 150). ?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", 291). ?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(7, Client) of {some, Queue} -> telega@internal@request_queue:shutdown(Queue); none -> nil end. -file("src/telega/client.gleam", 301). ?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(7, Client) of {some, Queue} -> telega@internal@request_queue:total_length(Queue); none -> 0 end. -file("src/telega/client.gleam", 311). ?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(7, Client) of {some, Queue} -> telega@internal@request_queue:is_overheated(Queue); none -> false end. -file("src/telega/client.gleam", 318). -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) -> Method = erlang:element(4, Api_request), gleam@result:'try'( api_to_request(Api_request), fun(Api_request@1) -> Request_id = telega@internal@utils:random_string(32), fetch_with_telemetry( Method, fun() -> case erlang:element(7, 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, Method, Api_request@1, erlang:element(3, Client) ) end end ) end ). -file("src/telega/client.gleam", 454). -spec get_api_url(telegram_client()) -> binary(). get_api_url(Client) -> erlang:element(4, Client). -file("src/telega/client.gleam", 483). -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", 467). -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, Path}. -file("src/telega/client.gleam", 475). -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, Path}.