-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 => <>, 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 ).