Current section
Files
Jump to
Current section
Files
src/telega_wisp.erl
-module(telega_wisp).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/telega_wisp.gleam").
-export([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_wisp.gleam", 103).
-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_wisp.gleam", 114).
-spec is_bot_request(
telega:telega(any(), any(), any()),
gleam@http@request:request(wisp@internal:connection())
) -> boolean().
is_bot_request(Telega, Req) ->
Path = begin
_pipe = fun gleam@http@request:path_segments/1(Req),
gleam@string:join(_pipe, <<"/"/utf8>>)
end,
telega:is_webhook_path(Telega, Path).
-file("src/telega_wisp.gleam", 87).
?DOC(
" Common webhook gate shared by `handle_bot` and `handle_bot_with_reply`:\n"
" non-webhook paths go to `next`, then the JSON body is required, the secret\n"
" token validated (401), and updates are rejected with 503 while the bot is\n"
" draining (graceful shutdown) so Telegram retries them after the deploy\n"
" instead of losing them.\n"
).
-spec accept_bot_request(
telega:telega(any(), any(), any()),
gleam@http@request:request(wisp@internal:connection()),
fun(() -> gleam@http@response:response(wisp:body())),
fun((gleam@dynamic:dynamic_()) -> gleam@http@response:response(wisp:body()))
) -> gleam@http@response:response(wisp:body()).
accept_bot_request(Telega, Req, Next, Run) ->
gleam@bool:lazy_guard(
not is_bot_request(Telega, Req),
Next,
fun() ->
wisp:require_json(
Req,
fun(Json) ->
gleam@bool:lazy_guard(
not is_secret_token_valid(Telega, Req),
fun() -> wisp:response(401) end,
fun() ->
gleam@bool:lazy_guard(
telega:is_draining(Telega),
fun() -> wisp:response(503) end,
fun() -> Run(Json) end
)
end
)
end
)
end
).
-file("src/telega_wisp.gleam", 30).
?DOC(
" A middleware function to handle incoming requests from the Telegram API.\n"
" Handles a request to the bot webhook endpoint, decodes the incoming message,\n"
" validates the secret token, and passes the message to the bot for processing.\n"
"\n"
" ```gleam\n"
" import wisp.{type Request, type Response}\n"
" import telega.{type Bot}\n"
" import telega_wisp\n"
"\n"
" fn handle_request(bot: Bot, req: Request) -> Response {\n"
" use <- telega_wisp.handle_bot(req, bot)\n"
" // ...\n"
" }\n"
" ```\n"
).
-spec handle_bot(
telega:telega(any(), any(), any()),
gleam@http@request:request(wisp@internal:connection()),
fun(() -> gleam@http@response:response(wisp:body()))
) -> gleam@http@response:response(wisp:body()).
handle_bot(Telega, Req, Handler) ->
accept_bot_request(
Telega,
Req,
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_wisp"/utf8>>,
function => <<"handle_bot"/utf8>>,
line => 45})
end end),
wisp:ok()
end
).
-file("src/telega_wisp.gleam", 64).
?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(wisp@internal:connection()),
integer(),
fun(() -> gleam@http@response:response(wisp:body()))
) -> gleam@http@response:response(wisp:body()).
handle_bot_with_reply(Telega, Req, Timeout, Handler) ->
accept_bot_request(
Telega,
Req,
Handler,
fun(Json) -> case telega@update:decode_raw(Json) of
{ok, Message} ->
case telega:handle_update_webhook(Telega, Message, Timeout) of
{json_response, Body} ->
wisp:json_response(Body, 200);
empty_response ->
wisp:ok()
end;
{error, _} ->
wisp:response(400)
end end
).