150 lines
3.6 KiB
Rust
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(¬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!(
|
|
"<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)
|
|
}
|
|
}
|