use envconfig::Envconfig; use log::{debug, info, warn}; use teloxide::{prelude::*, utils::command::BotCommand}; const VERSION: &str = env!("GIT_REVISION"); const BRANCH: &str = env!("GIT_BRANCH"); #[tokio::main] async fn main() { debug!("starting the application"); tokio::spawn(run()).await.unwrap(); } #[derive(Envconfig, Clone)] struct Settings { #[envconfig(from = "ALTEREGO_TELEGRAM_TOKEN")] pub telegram_token: String, #[envconfig( from = "ALTEREGO_CLIMATE_DSN", default = "http://127.0.0.1:18081/v1/home/temperature" )] pub climate_dsn: String, #[envconfig( from = "ALTEREGO_HOSTTEMP_CMD", default = "/opt/vc/bin/vcgencmd measure_temp" )] pub hosttemp_cmd: String, } async fn run() { env_logger::init(); let settings = Settings::init_from_env().expect("reading config values"); let startup = std::sync::Arc::from(std::time::SystemTime::now()); let bot = teloxide::Bot::builder() .token(&settings.telegram_token) .build(); let bot_name = "AlterEgo"; teloxide::commands_repl(bot, bot_name, move |cx, command| { let climate = settings.climate_dsn.clone(); let cmd: String = settings.hosttemp_cmd.clone(); let cmd: Vec<&str> = cmd.split(' ').collect(); let console_cmd = cmd.first().expect("getting console command").to_string(); let arg: String = cmd.get(1).unwrap_or(&"").to_string(); let startup = *startup; async move { handler(cx, command, climate, console_cmd, arg, startup).await } }) .await; } #[derive(serde::Deserialize, Debug)] struct Climate { humidity: f32, temp: f32, } async fn handler( cx: UpdateWithCx, command: Command, dsn: String, console_command: String, console_arg: String, startup: std::time::SystemTime, ) -> ResponseResult<()> { let request_id = uuid::Uuid::new_v4(); info!( "incoming request xreqid={} command={:?}", request_id, command ); match command { Command::Help => cx.answer(Command::descriptions()).send().await?, Command::HostTemperature => { info!( "querying command {} with arg {}", console_command, console_arg ); let cmd = std::process::Command::new(&console_command) .arg(&console_arg) .stdout(std::process::Stdio::piped()) .spawn() .expect("running vcgencmd command"); let output = cmd.wait_with_output().expect("waiting for output"); let parsed = std::string::String::from_utf8(output.stdout).expect("casting into string"); let parsed = parsed.replace("temp=", ""); cx.answer_str(format!("Your Raspberry PI temperature is {}", parsed)) .await? } Command::RoomTemperature => { info!("sending request to {}", dsn); let response = match reqwest::get(&dsn).await { Ok(response) => response, Err(err) => { warn!( "unable to handle request xreqid={} error={:?}", request_id, err ); cx.answer_str(format!("something went wrong, reference to {}", request_id)) .await?; return Err(RequestError::NetworkError(err)); } }; let info: Climate = match response.json::().await { Ok(result) => result, Err(err) => { warn!( "unable to handle request xreqid={} error={:?}", request_id, err ); cx.answer_str(format!("something went wrong, reference to {}", request_id)) .await?; return Err(RequestError::NetworkError(err)); } }; debug!("parsed value: {:?}", info); cx.answer_str(format!( "Your room temperature is {:.2} and humidity is {:.2}.", info.temp, info.humidity )) .await? } Command::VersionRequest => { cx.answer_str(format!( "app version is {}@{}, uptime is {} second(-s)", VERSION, BRANCH, startup.elapsed().unwrap().as_secs() )) .await? } }; Ok(()) } #[derive(BotCommand, Debug)] #[command(rename = "lowercase", description = "These commands are supported:")] enum Command { #[command(description = "display this text.")] Help, #[command(description = "temperature of your room.")] RoomTemperature, #[command(description = "temperature of raspberry.")] HostTemperature, #[command(description = "prints current version.")] VersionRequest, }