/*
* Copyright (C) 2025 Jonni Liljamo <jonni@liljamo.com>
*
* This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
* more information.
*/
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::json;
use crate::{
MessageForm,
config::{ServiceConfig, deserialize_token},
};
use super::Service;
const COMMONMARK_OPTIONS: pulldown_cmark::Options =
pulldown_cmark::Options::ENABLE_TABLES.union(pulldown_cmark::Options::ENABLE_STRIKETHROUGH);
#[derive(Clone, Deserialize, Serialize)]
pub struct MatrixConfig {
pub instance: String,
#[serde(deserialize_with = "deserialize_token")]
pub token: String,
pub room: String,
}
#[typetag::serde(name = "matrix")]
impl ServiceConfig for MatrixConfig {
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
pub struct MatrixService {
client: reqwest::Client,
config: MatrixConfig,
}
impl MatrixService {
pub fn new(client: reqwest::Client, config: MatrixConfig) -> Self {
Self { client, config }
}
fn gen_txn_id(&self) -> String {
format!(
"{}_{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or(std::time::Duration::new(0, 0))
.as_nanos()
)
}
}
#[async_trait]
impl Service for MatrixService {
async fn send(&self, form: &MessageForm) -> Result<(), Box<dyn std::error::Error>> {
let body = if form.format_commonmark {
let bold_title = format!("**{}**", form.title.trim());
let title_parser = pulldown_cmark::Parser::new_ext(&bold_title, COMMONMARK_OPTIONS);
let mut html_title = String::new();
pulldown_cmark::html::push_html(&mut html_title, title_parser);
let body_parser =
pulldown_cmark::Parser::new_ext(form.message.trim(), COMMONMARK_OPTIONS);
let mut html_body = String::new();
pulldown_cmark::html::push_html(&mut html_body, body_parser);
json!({
"msgtype": "m.notice",
"body": format!("{}\n\n{}", form.title, form.message),
"format": "org.matrix.custom.html",
"formatted_body": format!("{}\n{}", html_title, html_body),
})
} else {
json!({
"msgtype": "m.notice",
"body": format!("**{}**\n\n{}", form.title, form.message),
"format": "org.matrix.custom.html",
"formatted_body": format!("<p><strong>{}</strong></p>\n<p>{}</p>", form.title, form.message),
})
};
let _ = self
.client
.put(format!(
"{}/_matrix/client/v3/rooms/{}/send/m.room.message/{}",
self.config.instance,
self.config.room,
self.gen_txn_id()
))
.query(&[("access_token", &self.config.token)])
.body(body.to_string())
.send()
.await?;
Ok(())
}
}