// The version of Heroku ping-pong-bot, which uses a webhook to receive updates // from Telegram, instead of long polling. // // You will need to configure the buildpack for heroku. We will be using Heroku // rust buildpack [1]. Configuration was done by using heroku CLI. // // If you're creating a new Heroku application, run this: // // ``` // heroku create --buildpack emk/rust // ``` // // To set buildpack for existing applicaton: // // ``` // heroku buildpacks:set emk/rust // ``` // // [1] https://github.com/emk/heroku-buildpack-rust // TODO: use built-in webhook support use teloxide::{ dispatching::{ stop_token::AsyncStopToken, update_listeners::{self, StatefulListener}, }, prelude::*, types::Update, }; use std::{convert::Infallible, env, net::SocketAddr}; use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; use warp::Filter; use reqwest::{StatusCode, Url}; #[tokio::main] async fn main() { pretty_env_logger::init(); log::info!("Starting heroku_ping_pong_bot..."); let bot = Bot::from_env().auto_send(); teloxide::repl_with_listener( bot.clone(), |msg: Message, bot: AutoSend| async move { bot.send_message(msg.chat.id, "pong").await?; respond(()) }, webhook(bot).await, ) .await; } async fn handle_rejection(error: warp::Rejection) -> Result { log::error!("Cannot process the request due to: {:?}", error); Ok(StatusCode::INTERNAL_SERVER_ERROR) } pub async fn webhook(bot: AutoSend) -> impl update_listeners::UpdateListener { // Heroku auto defines a port value let teloxide_token = env::var("TELOXIDE_TOKEN").expect("TELOXIDE_TOKEN env variable missing"); let port: u16 = env::var("PORT") .expect("PORT env variable missing") .parse() .expect("PORT value to be integer"); // Heroku host example .: "heroku-ping-pong-bot.herokuapp.com" let host = env::var("HOST").expect("have HOST env variable"); let path = format!("bot{teloxide_token}"); let url = Url::parse(&format!("https://{host}/{path}")).unwrap(); bot.set_webhook(url).await.expect("Cannot setup a webhook"); let (tx, rx) = mpsc::unbounded_channel(); let server = warp::post() .and(warp::path(path)) .and(warp::body::json()) .map(move |update: Update| { tx.send(Ok(update)).expect("Cannot send an incoming update from the webhook"); StatusCode::OK }) .recover(handle_rejection); let (stop_token, stop_flag) = AsyncStopToken::new_pair(); let addr = format!("0.0.0.0:{port}").parse::().unwrap(); let server = warp::serve(server); let (_addr, fut) = server.bind_with_graceful_shutdown(addr, stop_flag); // You might want to use serve.key_path/serve.cert_path methods here to // setup a self-signed TLS certificate. tokio::spawn(fut); let stream = UnboundedReceiverStream::new(rx); fn streamf(state: &mut (S, T)) -> &mut S { &mut state.0 } StatefulListener::new((stream, stop_token), streamf, |state: &mut (_, AsyncStopToken)| { state.1.clone() }) }