Current section
Files
Jump to
Current section
Files
src/telega_mist.erl
-module(telega_mist).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/telega_mist.gleam").
-export([handle_bot_with_limit/4, handle_bot/3, handle_bot_with_reply/4]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/telega_mist.gleam", 149).
-spec empty_response(integer()) -> gleam@http@response:response(mist:response_data()).
empty_response(Status) ->
_pipe = gleam@http@response:new(Status),
gleam@http@response:set_body(_pipe, {bytes, gleam@bytes_tree:new()}).
-file("src/telega_mist.gleam", 160).
-spec is_secret_token_valid(
telega:telega(any(), any(), any()),
gleam@http@request:request(any())
) -> boolean().
is_secret_token_valid(Telega, Req) ->
Secret_header_value = begin
_pipe = gleam@http@request:get_header(
Req,
<<"x-telegram-bot-api-secret-token"/utf8>>
),
gleam@result:unwrap(_pipe, <<""/utf8>>)
end,
telega:is_secret_token_valid(Telega, Secret_header_value).
-file("src/telega_mist.gleam", 171).
-spec is_bot_request(
telega:telega(any(), any(), any()),
gleam@http@request:request(mist@internal@http:connection())
) -> boolean().
is_bot_request(Telega, Req) ->
Path = begin
_pipe = gleam@http@request:path_segments(Req),
gleam@string:join(_pipe, <<"/"/utf8>>)
end,
telega:is_webhook_path(Telega, Path).
-file("src/telega_mist.gleam", 124).
?DOC(
" Common webhook gate shared by `handle_bot*` handlers: non-webhook paths go\n"
" to `next`, then the secret token is validated (401), updates are rejected\n"
" with 503 while the bot is draining (graceful shutdown) so Telegram retries\n"
" them after the deploy instead of losing them, and finally the body is read\n"
" and parsed as JSON (400 on failure).\n"
).
-spec accept_bot_request(
telega:telega(any(), any(), any()),
gleam@http@request:request(mist@internal@http:connection()),
integer(),
fun(() -> gleam@http@response:response(mist:response_data())),
fun((gleam@dynamic:dynamic_()) -> gleam@http@response:response(mist:response_data()))
) -> gleam@http@response:response(mist:response_data()).
accept_bot_request(Telega, Req, Max_body_limit, Next, Run) ->
gleam@bool:lazy_guard(
not is_bot_request(Telega, Req),
Next,
fun() ->
gleam@bool:lazy_guard(
not is_secret_token_valid(Telega, Req),
fun() -> empty_response(401) end,
fun() ->
gleam@bool:lazy_guard(
telega:is_draining(Telega),
fun() -> empty_response(503) end,
fun() -> case mist:read_body(Req, Max_body_limit) of
{ok, Req@1} ->
case gleam@json:parse_bits(
erlang:element(4, Req@1),
{decoder,
fun gleam@dynamic@decode:decode_dynamic/1}
) of
{ok, Json} ->
Run(Json);
{error, _} ->
empty_response(400)
end;
{error, _} ->
empty_response(400)
end end
)
end
)
end
).
-file("src/telega_mist.gleam", 66).
?DOC(" Same as `handle_bot`, but lets you set the maximum request body size in bytes.\n").
-spec handle_bot_with_limit(
telega:telega(any(), any(), any()),
gleam@http@request:request(mist@internal@http:connection()),
integer(),
fun(() -> gleam@http@response:response(mist:response_data()))
) -> gleam@http@response:response(mist:response_data()).
handle_bot_with_limit(Telega, Req, Max_body_limit, Handler) ->
accept_bot_request(
Telega,
Req,
Max_body_limit,
Handler,
fun(Json) ->
proc_lib:spawn_link(fun() -> case telega@update:decode_raw(Json) of
{ok, Message} ->
telega:handle_update(Telega, Message),
nil;
{error, E} ->
erlang:error(#{gleam_error => panic,
message => (<<"Failed to decode update"/utf8,
(telega@error:to_string(E))/binary>>),
file => <<?FILEPATH/utf8>>,
module => <<"telega_mist"/utf8>>,
function => <<"handle_bot_with_limit"/utf8>>,
line => 82})
end end),
empty_response(200)
end
).
-file("src/telega_mist.gleam", 52).
?DOC(
" A handler to process incoming requests from the Telegram API directly on top\n"
" of [mist](https://hexdocs.pm/mist/), without wisp — for minimalistic\n"
" deployments.\n"
"\n"
" It checks the webhook path, validates the secret token, decodes the incoming\n"
" update, and dispatches it to the bot in a separate process so the `200 OK`\n"
" response is returned immediately (Telegram waits for the response before\n"
" sending the next update).\n"
"\n"
" ```gleam\n"
" import gleam/http/request.{type Request}\n"
" import gleam/http/response.{type Response}\n"
" import mist.{type Connection, type ResponseData}\n"
" import telega.{type Telega}\n"
" import telega_mist\n"
"\n"
" fn handle_request(\n"
" req: Request(Connection),\n"
" bot: Telega(session, error, dependencies),\n"
" ) -> Response(ResponseData) {\n"
" use <- telega_mist.handle_bot(telega: bot, req:)\n"
"\n"
" // Your other routes here...\n"
" response.new(404) |> response.set_body(mist.Bytes(bytes_tree.new()))\n"
" }\n"
" ```\n"
).
-spec handle_bot(
telega:telega(any(), any(), any()),
gleam@http@request:request(mist@internal@http:connection()),
fun(() -> gleam@http@response:response(mist:response_data()))
) -> gleam@http@response:response(mist:response_data()).
handle_bot(Telega, Req, Handler) ->
handle_bot_with_limit(Telega, Req, 4000000, Handler).
-file("src/telega_mist.gleam", 154).
-spec json_response(binary()) -> gleam@http@response:response(mist:response_data()).
json_response(Body) ->
_pipe = gleam@http@response:new(200),
_pipe@1 = gleam@http@response:set_header(
_pipe,
<<"content-type"/utf8>>,
<<"application/json"/utf8>>
),
gleam@http@response:set_body(_pipe@1, {bytes, gleam_stdlib:wrap_list(Body)}).
-file("src/telega_mist.gleam", 101).
?DOC(
" Like `handle_bot`, but lets the handler answer the update directly in the\n"
" webhook HTTP response body ([webhook reply](https://core.telegram.org/bots/api#making-requests-when-getting-updates)),\n"
" saving one HTTP round-trip for the first eligible API call.\n"
"\n"
" Unlike `handle_bot`, the request process waits up to `timeout` ms for the\n"
" handler to either claim a reply or finish; after the timeout it answers an\n"
" empty `200 OK` and the handler keeps running in the background. Pick a\n"
" `timeout` safely below Telegram's webhook timeout — e.g. 5000 ms.\n"
"\n"
" > ⚠️ A claimed call resolves to a synthetic stub inside the handler (`True`\n"
" > for boolean methods, a fake `Message` for `sendMessage`).\n"
" > Full guide in telega's `telega/webhook_reply` module docs.\n"
).
-spec handle_bot_with_reply(
telega:telega(any(), any(), any()),
gleam@http@request:request(mist@internal@http:connection()),
integer(),
fun(() -> gleam@http@response:response(mist:response_data()))
) -> gleam@http@response:response(mist:response_data()).
handle_bot_with_reply(Telega, Req, Timeout, Handler) ->
accept_bot_request(
Telega,
Req,
4000000,
Handler,
fun(Json) -> case telega@update:decode_raw(Json) of
{ok, Message} ->
case telega:handle_update_webhook(Telega, Message, Timeout) of
{json_response, Body} ->
json_response(Body);
empty_response ->
empty_response(200)
end;
{error, _} ->
empty_response(400)
end end
).