added matrix messages to do_reminder and do_announcement and improved their html formatting

This commit is contained in:
murmeldin 2025-01-14 17:40:04 +01:00
parent 1577d5de2e
commit c2d9dcdd74
4 changed files with 68 additions and 72 deletions

1
Cargo.lock generated
View file

@ -1483,6 +1483,7 @@ dependencies = [
"colored", "colored",
"futures", "futures",
"headers", "headers",
"lazy_static",
"lettre", "lettre",
"log", "log",
"mediawiki", "mediawiki",

View file

@ -24,6 +24,7 @@ nom = "7.1.3"
mediawiki = "0.3.1" mediawiki = "0.3.1"
ollama-rs = "0.2.1" ollama-rs = "0.2.1"
tokio = "1.0.0" tokio = "1.0.0"
lazy_static = "1.4"
[[bin]] [[bin]]
name = "Plenum-Bot" name = "Plenum-Bot"

View file

@ -59,7 +59,7 @@ const CONFIG_SPEC: CfgSpec<'static> = CfgSpec {
description: "Various strings used.", description: "Various strings used.",
fields: &[ fields: &[
CfgField::Default { key: "email-greeting", CfgField::Default { key: "email-greeting",
default: "Hallo liebe Mitreisende,", default: "Hallo liebe Mitreisenden,",
description: "\"Hello\"-greeting added at the start of every email.", description: "\"Hello\"-greeting added at the start of every email.",
}, },
CfgField::Default { key: "email-signature", CfgField::Default { key: "email-signature",
@ -400,13 +400,23 @@ fn do_announcement(
"Falls ihr noch Themen ergänzen wollt ist hier der Link zum Pad:\n {}", "Falls ihr noch Themen ergänzen wollt ist hier der Link zum Pad:\n {}",
hedgedoc.format_url(&current_pad_id) hedgedoc.format_url(&current_pad_id)
); );
let body = format!("{line1}\n\n{line2}"); let mut body = format!("{line1}\n\n{line2}");
// send it // send it
let message_id = send_email(&subject, &body, email, config)?; let message_id = send_email(&subject, &body, email, config)?;
// on success, update state (ignore write errors, they'll be checked later) // on success, update state (ignore write errors, they'll be checked later)
config.set("email-message-id", &message_id).ok(); config.set("email-message-id", &message_id).ok();
config.set("state-name", &ProgramState::Announced.to_string()).ok(); config.set("state-name", &ProgramState::Announced.to_string()).ok();
config.set("state-toc", &toc).ok(); config.set("email-state-toc", &toc).ok();
let mut matrix = MatrixClient::new(
&config["matrix-homeserver-url"],
&config["matrix-user-id"],
&config["matrix-access-token"],
&config["matrix-room-id-1"],
&config["matrix-room-id-2"],
is_dry_run(),
);
let message = format!("{}\n\n{}{}",&config["text-email-greeting"], &body, &config["text-email-signature"]);
matrix.send_message_to_two_rooms(&message)?;
Ok(()) Ok(())
} }
@ -436,7 +446,7 @@ fn do_reminder(
} else { } else {
format!("Es gab nochmal Änderungen, die aktualisierten Themen für das Plenum sind:\n\n{toc}\n\n") format!("Es gab nochmal Änderungen, die aktualisierten Themen für das Plenum sind:\n\n{toc}\n\n")
}; };
let body = if n_topics > 0 { let mut body = if n_topics > 0 {
format!( format!(
"{body_prefix}Die vollen Details findet ihr im Pad:\n {}\n\nBis {} um 20:00!", "{body_prefix}Die vollen Details findet ihr im Pad:\n {}\n\nBis {} um 20:00!",
hedgedoc.format_url(&current_pad_id), hedgedoc.format_url(&current_pad_id),
@ -457,10 +467,21 @@ fn do_reminder(
NYI!( NYI!(
"do we skip ahead to ProgramState::Logged here or do we later add a note to the wiki?" "do we skip ahead to ProgramState::Logged here or do we later add a note to the wiki?"
); );
// TODO: ADD SOMETHING TO WIKI
} }
config.set("state-name", &ProgramState::Reminded.to_string()).ok(); config.set("state-name", &ProgramState::Reminded.to_string()).ok();
config.set("state-toc", &toc).ok(); config.set("email-state-toc", &toc).ok();
let mut matrix = MatrixClient::new(
&config["matrix-homeserver-url"],
&config["matrix-user-id"],
&config["matrix-access-token"],
&config["matrix-room-id-1"],
&config["matrix-room-id-2"],
is_dry_run(),
);
let message = format!("{}\n\n{}{}",&config["text-email-greeting"], &body, &config["text-email-signature"]);
matrix.send_message_to_two_rooms(&message)?;
Ok(()) Ok(())
} }
@ -501,7 +522,7 @@ fn do_protocol(
let pad_content = pad_content.replace("[toc]", &toc); let pad_content = pad_content.replace("[toc]", &toc);
let body = format!( let body = format!(
"Anbei das Protokoll vom {human_date}, ab sofort auch im Wiki zu finden.\n\n\ "Anbei das Protokoll vom {human_date}, ab sofort auch im Wiki zu finden.\n\n\
Das Pad für das nächste Plenum ist zu finden unter <{}/{}>.\nDie Protokolle der letzten Plena findet ihr im wiki unter <{}/index.php?title={}>.\n\n---Protokoll:---\n{}\n-----", Das Pad für das nächste Plenum ist zu finden unter <{}/{}>.\n\nDie Protokolle der letzten Plena findet ihr im wiki unter <{}/index.php?title={}>.\n\n---Protokoll:---\n{}\n-----",
&config["hedgedoc-server-url"], &config["hedgedoc-server-url"],
&config["hedgedoc-next-id"], &config["hedgedoc-next-id"],
&config["wiki-server-url"], &config["wiki-server-url"],
@ -518,7 +539,7 @@ fn do_protocol(
let pad_content = pad_content.replace("[toc]", &toc); let pad_content = pad_content.replace("[toc]", &toc);
let body = format!( let body = format!(
"Anbei das Protokoll vom {human_date}, ab sofort auch im Wiki zu finden.\n\n\ "Anbei das Protokoll vom {human_date}, ab sofort auch im Wiki zu finden.\n\n\
Das Pad für das nächste Plenum ist zu finden unter {}/{}.\nDie Protokolle der letzten Plena findet ihr im wiki unter {}/index.php?title={}.\n\n---Protokoll:---{}", Das Pad für das nächste Plenum ist zu finden unter {}/{}.\n\nDie Protokolle der letzten Plena findet ihr im wiki unter {}/index.php?title={}.\n\n---Protokoll:---{}",
&config["hedgedoc-server-url"], &config["hedgedoc-server-url"],
&config["hedgedoc-next-id"], &config["hedgedoc-next-id"],
&config["wiki-server-url"], &config["wiki-server-url"],
@ -533,39 +554,24 @@ fn do_protocol(
&config["matrix-homeserver-url"], &config["matrix-homeserver-url"],
&config["matrix-user-id"], &config["matrix-user-id"],
&config["matrix-access-token"], &config["matrix-access-token"],
&config["matrix-room-id-for-short-messages"], &config["matrix-room-id-1"],
&config["matrix-room-id-for-long-messages"], &config["matrix-room-id-2"],
is_dry_run(), is_dry_run(),
); );
// Send the matrix room message // Send the matrix room message
let human_date = plenum_day.format("%d.%m.%Y"); let human_date = plenum_day.format("%d.%m.%Y");
let pad_content = pad_content.replace("[toc]", &toc); let pad_content = pad_content.replace("[toc]", &toc);
let long_message = format!( let message = format!(
"Anbei das Protokoll vom {human_date}, ab sofort auch im Wiki zu finden.\n\n\ "Anbei das Protokoll vom {human_date}, ab sofort auch im Wiki zu finden.\n\n\
Das Pad für das nächste Plenum ist zu finden unter {}/{}.\nDie Protokolle der letzten Plena findet ihr im wiki unter {}/index.php?title={}.\n**Hier die Zusammenfassung:**\n{}", Das Pad für das nächste Plenum ist zu finden unter {}/{}.\n\nDie Protokolle der letzten Plena findet ihr im wiki unter {}/index.php?title={}.\n\n**Hier die Zusammenfassung:**\n\n{}",
&config["hedgedoc-server-url"], &config["hedgedoc-server-url"],
&config["hedgedoc-next-id"], &config["hedgedoc-next-id"],
&config["wiki-server-url"], &config["wiki-server-url"],
&config["wiki-plenum-page"], &config["wiki-plenum-page"],
&summary_or_toc &summary_or_toc
); );
let full_long_message = format!( let full_message = format!("{}\n\n{}{}",&config["text-email-greeting"], &message, &config["text-email-signature"]);
"{}\n{}{}", matrix.send_message_to_two_rooms(&full_message)?;
&config["text-email-greeting"], long_message, &config["text-email-signature"]
);
let short_message = format!(
"Das letzte Plenum hatte Anbei das Protokoll vom {human_date}, ab sofort auch im Wiki zu finden.\n\n\
Das Pad für das nächste Plenum ist zu finden unter {}/{}.\nDie Protokolle der letzten Plena findet ihr im wiki unter {}/index.php?title={}.",
&config["hedgedoc-server-url"],
&config["hedgedoc-next-id"],
&config["wiki-server-url"],
&config["wiki-plenum-page"]
);
let full_short_message = format!(
"{}\n{}{}",
&config["text-email-greeting"], short_message, &config["text-email-signature"].strip_prefix("[").unwrap_or(&config["text-email-signature"]).strip_suffix("]").unwrap_or(&config["text-email-signature"])
);
matrix.send_short_and_long_messages_to_two_rooms(&full_short_message, &full_long_message)?;
Ok(()) Ok(())
} }

View file

@ -1,5 +1,6 @@
use std::error::Error; use std::error::Error;
use std::io::Read; use std::io::Read;
use lazy_static::lazy_static;
use colored::Colorize; use colored::Colorize;
use regex::Regex; use regex::Regex;
@ -32,12 +33,12 @@ pub const CONFIG: CfgGroup<'static> = CfgGroup {
description: "Access Token / \"password\" used for authenticating as the bot.", description: "Access Token / \"password\" used for authenticating as the bot.",
}, },
CfgField::Default { CfgField::Default {
key: "room-id-for-long-messages", key: "room-id-1",
default: "!someLongRoomIdentifier:matrix.org", default: "!someLongRoomIdentifier:matrix.org",
description: "API Username associated with the bot account used for writing messages.", description: "API Username associated with the bot account used for writing messages.",
}, },
CfgField::Default { CfgField::Default {
key: "room-id-for-short-messages", key: "room-id-2",
default: "!someLongRoomIdentifier:matrix.org", default: "!someLongRoomIdentifier:matrix.org",
description: "API Username associated with the bot account used for writing messages.", description: "API Username associated with the bot account used for writing messages.",
}, },
@ -51,8 +52,8 @@ pub struct MatrixClient {
is_dry_run: bool, is_dry_run: bool,
client: Client, client: Client,
txn_id: u64, txn_id: u64,
room_id_for_short_messages: String, room_id_1: String,
room_id_for_long_messages: String, room_id_2: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -82,8 +83,8 @@ impl std::fmt::Debug for MatrixClient {
impl MatrixClient { impl MatrixClient {
pub fn new( pub fn new(
homeserver_url: &str, user_id: &str, access_token: &str, room_id_for_short_messages: &str, homeserver_url: &str, user_id: &str, access_token: &str, room_id_1: &str,
room_id_for_long_messages: &str, is_dry_run: bool, room_id_2: &str, is_dry_run: bool,
) -> Self { ) -> Self {
Self { Self {
homeserver_url: homeserver_url.to_string(), homeserver_url: homeserver_url.to_string(),
@ -92,8 +93,8 @@ impl MatrixClient {
is_dry_run, is_dry_run,
client: Client::builder().cookie_store(true).build().unwrap(), client: Client::builder().cookie_store(true).build().unwrap(),
txn_id: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(), txn_id: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
room_id_for_long_messages: room_id_for_long_messages.to_string(), room_id_2: room_id_2.to_string(),
room_id_for_short_messages: room_id_for_short_messages.to_string(), room_id_1: room_id_1.to_string(),
} }
} }
fn request<T: Serialize>( fn request<T: Serialize>(
@ -102,7 +103,7 @@ impl MatrixClient {
) -> Result<Value, Box<dyn Error>> { ) -> Result<Value, Box<dyn Error>> {
let client = reqwest::blocking::Client::new(); let client = reqwest::blocking::Client::new();
let url = format!("{}/_matrix/client{}", self.homeserver_url, endpoint); let url = format!("{}/_matrix/client{}", self.homeserver_url, endpoint);
print!("url: {}", url.yellow()); verboseln!("url: {}\n", url.blue());
// Construct URL with query parameters // Construct URL with query parameters
let mut request = client.request(method, &url); let mut request = client.request(method, &url);
@ -176,41 +177,28 @@ impl MatrixClient {
current current
} }
pub fn pandoc_convert_md_to_html(markdown: String) -> Result<String, Box<dyn Error>> { pub fn format_text_to_html(markdown: String) -> Result<String, Box<dyn Error>> {
let (output, errors, status) = crate::pipe( lazy_static! {
"pandoc", static ref MATRIX_BOLD: Regex = Regex::new(r"\*\*(.+?)\*\*").unwrap();
&mut [ static ref MATRIX_ITALIC: Regex = Regex::new(r"\*(.+?)\*").unwrap();
"--from", "markdown-auto_identifiers", static ref MATRIX_URL: Regex = Regex::new(r"(https?://\S+?)(?:[.,])?(?:\s|$)").unwrap();
"--to", "html5",
"--wrap=none", // Verhindert Zeilenumbrüche
"--no-highlight", // Deaktiviert Syntax-Highlighting
],
markdown,
)?;
if status.success() {
println!("Resultat von Pandoc: {}", output);
Ok(output)
} else {
Err(format!("Pandoc error, exit code {:?}\n{}", status, errors).into())
} }
}
pub fn format_matrix_html(markdown: String) -> Result<String, Box<dyn Error>> {
let mut html = Self::pandoc_convert_md_to_html(markdown)?;
let url_regex = Regex::new(r"(https?://[^\s<]+)")?; let mut html = markdown;
html = url_regex.replace_all(&html, r#"<a href="$1">$1</a>"#).to_string();
html = html.replace("\n\n", "</p><p>"); // Handle URLs first
html = MATRIX_URL.replace_all(&html, r"<a href='$1'>$1</a>").to_string();
// Basic formatting
html = MATRIX_BOLD.replace_all(&html, r"<b>$1</b>").to_string();
html = MATRIX_ITALIC.replace_all(&html, r"<i>$1</i>").to_string();
// Convert newlines to <br>
html = html.replace("\n", "<br>"); html = html.replace("\n", "<br>");
html = html.replace("**", "<strong>");
html = html.replace("__", "<strong>");
Ok(html) Ok(html)
} }
pub fn pandoc_convert_text_to_md(markdown: String) -> Result<String, Box<dyn Error>> {
pub fn pandoc_convert_text_to_md(markdown: String) -> Result<String, Box<dyn Error>> {
let (output, errors, status) = crate::pipe( let (output, errors, status) = crate::pipe(
"pandoc", "pandoc",
&mut ["--from", "markdown-auto_identifiers", "--to", "html5"], &mut ["--from", "markdown-auto_identifiers", "--to", "html5"],
@ -227,14 +215,14 @@ impl MatrixClient {
pub fn send_room_message( pub fn send_room_message(
&mut self, room_id: &str, text: &str, &mut self, room_id: &str, text: &str,
) -> Result<Value, Box<dyn Error>> { ) -> Result<Value, Box<dyn Error>> {
let formatted_text = Self::format_matrix_html(text.to_string())?; let formatted_text = Self::format_text_to_html(text.to_string())?;
let content = HashMap::from([ let content = HashMap::from([
("type", "m.room.message"), ("type", "m.room.message"),
("msgtype", "m.text"), ("msgtype", "m.text"),
("body", text), ("body", text),
("format", "org.matrix.custom.html"), ("format", "org.matrix.custom.html"),
("formatted_body", &formatted_text), ("formatted_body", &formatted_text),
("m.mentions", "{}"), // ("m.mentions", "{}"),
]); ]);
self.send_room_event(&room_id, "m.room.message", &content) self.send_room_event(&room_id, "m.room.message", &content)
} }
@ -243,14 +231,14 @@ impl MatrixClient {
&mut self, room: &str, event_type: &str, content: &impl Serialize, &mut self, room: &str, event_type: &str, content: &impl Serialize,
) -> Result<Value, Box<dyn Error>> { ) -> Result<Value, Box<dyn Error>> {
let endpoint = format!("/r0/rooms/{}/send/{}/{}", room, event_type, self.txn_id()); let endpoint = format!("/r0/rooms/{}/send/{}/{}", room, event_type, self.txn_id());
println!("room event:{}", &endpoint.red()); verboseln!("room event:{}", &endpoint.green());
self.put(&endpoint, None, Some(content), false) self.put(&endpoint, None, Some(content), false)
} }
pub fn send_short_and_long_messages_to_two_rooms( pub fn send_message_to_two_rooms(
&mut self, short_message: &str, long_message: &str, &mut self, message: &str
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
self.send_room_message( &self.room_id_for_long_messages.clone(), &long_message,)?; self.send_room_message( &self.room_id_2.clone(), &message,)?;
self.send_room_message( &self.room_id_for_short_messages.clone(), &short_message,)?; self.send_room_message( &self.room_id_1.clone(), &message,)?;
Ok(()) Ok(())
} }