support more recente teloxide version

This commit is contained in:
2023-03-05 17:33:53 +03:00
parent efede10312
commit 3f348d32cc
11 changed files with 647 additions and 126 deletions

View File

@ -1,18 +1,38 @@
#![allow(unused)]
mod climate;
mod repo;
mod utils;
use anyhow::Result;
use climate::SelfTemperature;
use envconfig::Envconfig;
use log::{debug, info, warn};
use teloxide::{prelude::*, utils::command::BotCommand};
use teloxide::{
dispatching::{update_listeners::AsUpdateStream, UpdateFilterExt},
dptree::di::Injectable,
filter_command,
payloads::SendMessage,
prelude::*,
utils::command::BotCommands,
};
use tokio_stream::StreamExt;
use crate::repo::Storage;
const VERSION: &str = env!("GIT_REVISION");
const BRANCH: &str = env!("GIT_BRANCH");
#[tokio::main]
async fn main() {
env_logger::init();
debug!("starting the application");
tokio::spawn(run()).await.unwrap();
}
#[derive(Envconfig, Clone)]
#[derive(Envconfig, Clone, Debug)]
struct Settings {
#[envconfig(from = "ALTEREGO_TELEGRAM_TOKEN")]
pub telegram_token: String,
@ -28,30 +48,200 @@ struct Settings {
default = "/opt/vc/bin/vcgencmd measure_temp"
)]
pub hosttemp_cmd: String,
#[envconfig(from = "ALTEREGO_DATABASE_URL", default = "./db.sqlite")]
pub db_source: String,
}
async fn run() {
env_logger::init();
async fn run() -> anyhow::Result<()> {
info!("starting");
let settings = Settings::init_from_env().expect("reading config values");
let startup = std::sync::Arc::from(std::time::SystemTime::now());
let bot = teloxide::Bot::new(&settings.telegram_token).auto_send();
let repo_config = repo::SqliteConfig {
source: settings.db_source,
..Default::default()
};
let bot = teloxide::Bot::builder()
.token(&settings.telegram_token)
.build();
let bot_name = "AlterEgo";
info!("repo config: {repo_config:?}");
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;
let sqlite_storage = repo::SqliteRepo::from_config(repo_config).await?;
async move { handler(cx, command, climate, console_cmd, arg, startup).await }
})
.await;
let climate_client = climate::Client::new(&settings.climate_dsn);
let self_temp_client = {
let splitted: Vec<&str> = settings.hosttemp_cmd.split(' ').into_iter().collect();
let (cmd, arg) = (splitted[0], splitted[1]);
climate::SelfTemperature::new(cmd, arg)
};
let handler = Update::filter_message()
.filter_command::<Command>()
.chain(dptree::filter(|msg: Message| msg.chat.is_private()))
.chain(dptree::filter_map_async(
|msg: Message, storage: repo::SqliteRepo| async move {
let chat_id = msg.chat.id.0;
info!("checking if the user {chat_id} exists");
let user = storage.load_user_by_chat_id(chat_id).await.unwrap();
match user {
Some(user) => Some(user),
None => {
let fname = msg.chat.first_name().unwrap_or_default();
let lname = msg.chat.last_name().unwrap_or_default();
info!("performing upsert for user {lname} {fname}");
let user_db = storage
.create_user(chat_id, [fname, lname].join(" "))
.await
.unwrap();
info!("upserted user {user_db:?}");
Some(user_db)
}
}
},
))
.branch(dptree::case![Command::RoomTemperature].endpoint(handle_temperature_sensor))
.branch(dptree::case![Command::HostTemperature].endpoint(handle_host_temperature))
.branch(dptree::case![Command::VersionRequest].endpoint(handle_version))
.branch(dptree::case![Command::Help].endpoint(handle_help));
let mut dependencies = DependencyMap::new();
dependencies.insert(sqlite_storage);
dependencies.insert(climate_client);
dependencies.insert(self_temp_client);
dependencies.insert(utils::Generators::new());
info!("running");
Dispatcher::builder(bot, handler)
.dependencies(dependencies)
.default_handler(|upd| async move {
warn!("unhandled update: {:?}", upd);
})
.build()
.setup_ctrlc_handler()
.dispatch()
.await;
Ok(())
}
type HandlerResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
fn error_msg(reqid: &utils::RequestID) -> String {
format!("There was an error handling command, sorry. Reffer to {reqid}")
}
async fn handle_temperature_sensor(
bot: AutoSend<Bot>,
msg: Message,
climate: climate::Client,
next_req_id: utils::Generators,
storage: repo::SqliteRepo,
user: repo::UserDB,
) -> Result<()> {
let chat_id = msg.chat.id;
let reqid = next_req_id.next_request_id();
let name = user.name;
bot.send_message(chat_id, format!("Just a second, {name}, asking..."))
.await?;
match climate.fetch().await {
Ok(temp) => {
let text = format!(
"Humidity is {} and temperature is {}",
temp.humidity, temp.temp,
);
bot.send_message(chat_id, text).await?;
}
Err(err) => {
warn!("[{reqid}] unable to fetch self_temp: {err}");
let msg = error_msg(&reqid);
bot.send_message(chat_id, msg).await?;
}
};
Ok(())
}
async fn handle_host_temperature(
bot: AutoSend<Bot>,
msg: Message,
temp: SelfTemperature,
next_req_id: utils::Generators,
) -> Result<()> {
let chat_id = msg.chat.id;
let reqid = next_req_id.next_request_id();
bot.send_message(chat_id, "Just a second, asking...")
.await?;
match temp.fetch().await {
Ok(temp) => {
let text = format!("Host temperature is {} degrees celcius", temp,);
bot.send_message(chat_id, text).await?;
}
Err(err) => {
warn!("[{reqid}] unable to fetch self_temp: {err}");
let msg = error_msg(&reqid);
bot.send_message(chat_id, msg).await?;
}
}
Ok(())
}
async fn handle_version(bot: AutoSend<Bot>, msg: Message) -> Result<()> {
let chat_id = msg.chat.id;
let text = format!("Bot version is {} (branch: {})", VERSION, BRANCH,);
bot.send_message(chat_id, text).await?;
Ok(())
}
async fn handle_help(bot: AutoSend<Bot>, msg: Message) -> Result<()> {
let chat_id = msg.chat.id;
bot.send_message(chat_id, Command::descriptions().to_string())
.await?;
Ok(())
}
struct Handler<S: repo::Storage + Clone + Send + Sync> {
storage: S,
climate: climate::Client,
self_temp: climate::SelfTemperature,
started: std::time::Instant,
}
fn log_error<'a, E: std::fmt::Display>(req_id: &'a str, msg: &'a str) -> impl FnOnce(E) -> E + 'a {
move |err: E| -> E {
warn!(
"request_id={}, {} err={}",
req_id.to_owned(),
msg.to_owned(),
err
);
err
}
}
fn log_message(req_id: &str, msg: Message) {
info!(
"message sent to chat_id={}, text={}",
msg.chat.id,
msg.text().unwrap_or_default(),
)
}
#[derive(serde::Deserialize, Debug)]
@ -60,99 +250,7 @@ struct Climate {
temp: f32,
}
async fn handler(
cx: UpdateWithCx<Message>,
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::<Climate>().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)]
#[derive(BotCommands, Debug, Clone, PartialEq, Eq)]
#[command(rename = "lowercase", description = "These commands are supported:")]
enum Command {
#[command(description = "display this text.")]