webcomment/src/notify.rs

150 lines
3.6 KiB
Rust

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<T> {
Some(T),
NoneSince(SystemTime),
}
impl<T> OptionSince<T> {
fn from_result<E, F: FnOnce(E)>(result: Result<T, E>, f: F) -> Self {
match result {
Ok(val) => Self::Some(val),
Err(e) => {
f(e);
Self::NoneSince(SystemTime::now())
}
}
}
}
impl<T> From<Option<T>> for OptionSince<T> {
fn from(opt: Option<T>) -> Self {
match opt {
Some(val) => Self::Some(val),
None => Self::NoneSince(SystemTime::now()),
}
}
}
struct Notifier {
matrix: Option<OptionSince<(matrix_sdk::Client, matrix_sdk::room::Joined)>>,
}
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(&notification.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!(
"<a href=\"{}{}t/{}\">New comment on topic \"<em>{}</em>\".</a>",
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<Notification>) {
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)
}
}