use crate::config::Config; use crossbeam_channel::Receiver; use log::error; use matrix_sdk::ruma; use std::time::{Duration, SystemTime}; pub struct Notification { pub topic: String, } enum OptionSince { Some(T), NoneSince(SystemTime), } impl OptionSince { fn from_result(result: Result, f: F) -> Self { match result { Ok(val) => Self::Some(val), Err(e) => { f(e); Self::NoneSince(SystemTime::now()) } } } } impl From> for OptionSince { fn from(opt: Option) -> Self { match opt { Some(val) => Self::Some(val), None => Self::NoneSince(SystemTime::now()), } } } struct Notifier { matrix: Option>, } impl Notifier { async fn new(config: &Config) -> Self { Self { matrix: if config.matrix_notify { Some(OptionSince::from_result(init_matrix(config).await, |e| { error!("Cannot init Matrix: {:?}", e) })) } else { None }, } } async fn notify(&mut self, config: &Config, notification: Notification) { match &self.matrix { None => {} Some(OptionSince::Some((_client, room))) => { let decoded_topic = tera::escape_html( &percent_encoding::percent_decode_str(¬ification.topic).decode_utf8_lossy(), ); if let Err(e) = room .send( ruma::events::room::message::RoomMessageEventContent::text_html( format!( "New comment on topic \"{}\": {}{}t/{}", decoded_topic, config.public_address, config.root_url, notification.topic, ), format!( "New comment on topic \"{}\".", config.public_address, config.root_url, notification.topic, decoded_topic, ), ), None, ) .await { error!("Sending Matrix message: {:?}", e); } } Some(OptionSince::NoneSince(earlier)) => { if SystemTime::now().duration_since(*earlier).unwrap() > Duration::from_secs(config.matrix_retry_timeout) { self.matrix = Some(OptionSince::from_result(init_matrix(config).await, |e| { error!("Cannot init Matrix: {:?}", e) })) } } } } } pub async fn run_notifier(config: &Config, recv: Receiver) { let mut notifier = Notifier::new(config).await; for notification in recv { notifier.notify(config, notification).await; } } #[derive(Debug)] enum MatrixError { CannotConnect(matrix_sdk::ClientBuildError), CannotLogin(matrix_sdk::Error), CannotSync(matrix_sdk::Error), RoomNotJoined, UnknownRoom, } async fn init_matrix( config: &Config, ) -> Result<(matrix_sdk::Client, matrix_sdk::room::Joined), MatrixError> { let user = ruma::UserId::parse(&config.matrix_user) .expect("Matrix username should be in format `@user:homeserver`"); let room_id = <&ruma::RoomId>::try_from(config.matrix_room.as_str()) .expect("Matrix room should be in format `#roomname:homeserver` or `!roomid:homeserver`"); let client = matrix_sdk::Client::builder() .homeserver_url(&config.matrix_server) .user_agent("Webcomment") .handle_refresh_tokens() .build() .await .map_err(MatrixError::CannotConnect)?; client .login_username(&user, &config.matrix_password) .send() .await .map_err(MatrixError::CannotLogin)?; client .sync_once(matrix_sdk::config::SyncSettings::default()) .await .map_err(MatrixError::CannotSync)?; let room = client.get_room(room_id).ok_or(MatrixError::UnknownRoom)?; if let matrix_sdk::room::Room::Joined(room) = room { Ok((client, room)) } else { Err(MatrixError::RoomNotJoined) } }