#![forbid(unsafe_code)] #![deny(unused_must_use)] #![warn(clippy::pedantic, clippy::as_conversions)] use std::path::{Path, PathBuf}; use bot::{Bot, Config}; use clap::Parser as _; use color_eyre::{eyre::Context, Result}; use matrix_sdk::Session; use serde::{Deserialize, Serialize}; use tracing::instrument; use xdg::BaseDirectories; mod bot; mod its_api; mod setup; /// The session data required by the bot to log in. Stored as TOML at the path /// given by [`session_path()`]. #[derive(Serialize, Deserialize, Debug)] pub struct SessionData { pub homeserver_url: Option, pub session: Session, } #[derive(clap::Parser)] struct Cli { #[arg(long, short = 'c')] config: Option, #[command(subcommand)] sub: Option, } #[derive(clap::Subcommand, Debug)] enum Subcommand { Setup(setup::Setup), } /// Returns the default path to the bot's configuration file. /// /// # Errors /// /// Returns an error if the directory containing the configuration file /// could not be created. fn default_config_path() -> Result { let base_dirs = BaseDirectories::with_prefix("its-matrix-bot")?; let path = base_dirs.place_config_file("config.toml")?; Ok(path) } /// Returns the path to the bot's session file. /// /// # Errors /// /// Returns an error if the directory containing the session file /// could not be created. fn session_path() -> Result { let base_dirs = BaseDirectories::with_prefix("its-matrix-bot")?; let path = base_dirs.place_data_file("state.toml")?; Ok(path) } /// Returns the path to the bot's internal database. /// /// # Errors /// /// Returns an error if no base directory could be determined (e.g. because /// no home directory is set). fn sled_store_path() -> Result { let base_dirs = BaseDirectories::with_prefix("gavel")?; Ok(base_dirs.get_data_home()) } #[instrument] async fn load_config(path: &Path) -> Result { let config = toml::from_str(&tokio::fs::read_to_string(path).await?)?; Ok(config) } fn install_tracing() { use tracing_subscriber::{ prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, EnvFilter, }; let fmt_layer = tracing_subscriber::fmt::layer().with_target(false); let filter_layer = tracing_subscriber::EnvFilter::try_from_default_env() .or_else(|_| EnvFilter::try_from("info")) .unwrap(); let error_layer = tracing_error::ErrorLayer::default(); tracing_subscriber::registry() .with(fmt_layer) .with(filter_layer) .with(error_layer) .init(); } #[tokio::main] async fn main() -> Result<()> { install_tracing(); color_eyre::install()?; let args = Cli::parse(); #[allow(clippy::single_match_else)] match args.sub { Some(Subcommand::Setup(setup)) => { setup::setup(setup).await?; Ok(()) } None => { let config_path = if let Some(config) = args.config { config } else { default_config_path()? }; let config = load_config(&config_path) .await .wrap_err("Failed to load bot configuration")?; let bot = Bot::new(config).await?; bot.run().await? } } }