//! Utility functions for working matrix-related tasks use std::{ fs::File, path::{Path, PathBuf}, }; use matrix_sdk::{ config::ClientConfig, reqwest::Url, ruma::{DeviceIdBox, UserId}, Client, }; use ron::ser::PrettyConfig; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Session { /// The homeserver URL pub homeserver: Url, /// Access token for authentication pub access_token: String, /// The user's mxid. pub user_id: UserId, /// The user's device ID. pub device_id: DeviceIdBox, } impl Session { /// Read a `Session` from the filesystem. pub fn from_fs() -> Result { let path = path()?.join("session.ron"); let file = File::open(&path)?; let session = ron::de::from_reader(file)?; Ok(session) } /// Create a matrix client and restore this session. pub fn restore(self) -> Result { let session = matrix_sdk::Session { access_token: self.access_token, user_id: self.user_id, device_id: self.device_id, }; let client = Client::new(self.homeserver)?; let runtime = tokio::runtime::Runtime::new().unwrap(); runtime.block_on(client.restore_login(session))?; Ok(client) } } /// Create a matrix client and log it in to the server at the given URL with the /// given credentials. pub fn login(url: &str, user: &str, password: &str) -> Result { let url: Url = if !url.contains("://") { format!("https://{}", url).parse() } else { url.parse() }?; let client = Client::new_with_config(url.clone(), config()?)?; let rt = tokio::runtime::Runtime::new().unwrap(); let response = rt.block_on(client.login(user, password, None, None))?; let session = Session { homeserver: url, access_token: response.access_token, user_id: response.user_id, device_id: response.device_id, }; let file = File::create(path()?.join("session.ron"))?; ron::ser::to_writer_pretty(file, &session, PrettyConfig::new())?; Ok(client) } /// Errors that can happen when logging in #[derive(Debug)] pub enum LoginError { /// Invalid URL Url(url::ParseError), /// Matrix SDK error Sdk(matrix_sdk::Error), /// I/O error Io(std::io::Error), /// Serialization error Ron(ron::Error), } impl std::fmt::Display for LoginError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { LoginError::Url(_) => write!(f, "Invalid homeserver address"), LoginError::Sdk(e) => write!(f, "{}", e), LoginError::Io(e) => write!(f, "Filesystem error: {}", e), LoginError::Ron(e) => write!(f, "Serialization error: {}", e), } } } impl std::error::Error for LoginError {} impl From for LoginError { fn from(e: url::ParseError) -> Self { LoginError::Url(e) } } impl From for LoginError { fn from(e: matrix_sdk::Error) -> Self { LoginError::Sdk(e) } } impl From for LoginError { fn from(e: std::io::Error) -> Self { LoginError::Io(e) } } impl From for LoginError { fn from(e: ron::Error) -> Self { LoginError::Ron(e) } } /// Configuration for `Clients`. fn config() -> Result { Ok(ClientConfig::new().store_path(&path()?)) } /// The path the the sdk store should be put in. fn path() -> Result { let path = Path::new(&std::env::var_os("HOME").unwrap()) .join(".config") .join("retrix"); std::fs::create_dir_all(&path)?; Ok(path) }