277 lines
7.6 KiB
Rust
277 lines
7.6 KiB
Rust
#![allow(unused)]
|
|
|
|
mod climate;
|
|
mod repo;
|
|
mod utils;
|
|
|
|
use anyhow::Result;
|
|
use climate::SelfTemperature;
|
|
use envconfig::Envconfig;
|
|
use log::{debug, info, warn};
|
|
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, Debug)]
|
|
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,
|
|
|
|
#[envconfig(from = "ALTEREGO_DATABASE_URL", default = "./db.sqlite")]
|
|
pub db_source: String,
|
|
}
|
|
|
|
async fn run() -> anyhow::Result<()> {
|
|
info!("starting");
|
|
|
|
let settings = Settings::init_from_env().expect("reading config values");
|
|
let bot = teloxide::Bot::new(&settings.telegram_token);
|
|
let repo_config = repo::SqliteConfig {
|
|
source: settings.db_source,
|
|
..Default::default()
|
|
};
|
|
|
|
info!("repo config: {repo_config:?}");
|
|
|
|
let sqlite_storage = repo::SqliteRepo::from_config(repo_config).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::ChatID].endpoint(handle_chat_id))
|
|
.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)
|
|
.enable_ctrlc_handler()
|
|
.default_handler(|upd| async move {
|
|
warn!("unhandled update: {:?}", upd);
|
|
})
|
|
.build()
|
|
.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: 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: 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: 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_chat_id(bot: Bot, msg: Message) -> Result<()> {
|
|
let chat_id = msg.chat.id;
|
|
let text = format!("Current chat id: {chat_id}");
|
|
|
|
bot.send_message(chat_id, text).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn handle_help(bot: 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)]
|
|
struct Climate {
|
|
humidity: f32,
|
|
temp: f32,
|
|
}
|
|
|
|
#[derive(BotCommands, Debug, Clone, PartialEq, Eq)]
|
|
#[command(description = "These commands are supported:")]
|
|
enum Command {
|
|
#[command(rename="help", description = "display this text.")]
|
|
Help,
|
|
#[command(rename="roomtemp", description = "temperature of your room.")]
|
|
RoomTemperature,
|
|
#[command(rename="hosttemp", description = "temperature of raspberry.")]
|
|
HostTemperature,
|
|
#[command(rename="version", description = "prints current version.")]
|
|
VersionRequest,
|
|
#[command(rename="chatid", description = "prints current chat id.")]
|
|
ChatID,
|
|
}
|