From c2d9dcdd744ae8bff63a6e8805df58eea383df8d Mon Sep 17 00:00:00 2001 From: murmeldin Date: Tue, 14 Jan 2025 17:40:04 +0100 Subject: [PATCH] added matrix messages to do_reminder and do_announcement and improved their html formatting --- Cargo.lock | 1 + Cargo.toml | 1 + src/main.rs | 62 ++++++++++++++++++++++------------------- src/matrix.rs | 76 ++++++++++++++++++++++----------------------------- 4 files changed, 68 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8cbba1..4c1d2bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1483,6 +1483,7 @@ dependencies = [ "colored", "futures", "headers", + "lazy_static", "lettre", "log", "mediawiki", diff --git a/Cargo.toml b/Cargo.toml index 7283a37..7ed1452 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ nom = "7.1.3" mediawiki = "0.3.1" ollama-rs = "0.2.1" tokio = "1.0.0" +lazy_static = "1.4" [[bin]] name = "Plenum-Bot" diff --git a/src/main.rs b/src/main.rs index df6c4c0..ca5ea33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,7 +59,7 @@ const CONFIG_SPEC: CfgSpec<'static> = CfgSpec { description: "Various strings used.", fields: &[ CfgField::Default { key: "email-greeting", - default: "Hallo liebe Mitreisende,", + default: "Hallo liebe Mitreisenden,", description: "\"Hello\"-greeting added at the start of every email.", }, 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 {}", hedgedoc.format_url(¤t_pad_id) ); - let body = format!("{line1}\n\n{line2}"); + let mut body = format!("{line1}\n\n{line2}"); // send it let message_id = send_email(&subject, &body, email, config)?; // on success, update state (ignore write errors, they'll be checked later) config.set("email-message-id", &message_id).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(()) } @@ -436,7 +446,7 @@ fn do_reminder( } else { 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!( "{body_prefix}Die vollen Details findet ihr im Pad:\n {}\n\nBis {} um 20:00!", hedgedoc.format_url(¤t_pad_id), @@ -457,10 +467,21 @@ fn do_reminder( NYI!( "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-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(()) } @@ -501,7 +522,7 @@ fn do_protocol( let pad_content = pad_content.replace("[toc]", &toc); let body = format!( "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-next-id"], &config["wiki-server-url"], @@ -518,7 +539,7 @@ fn do_protocol( let pad_content = pad_content.replace("[toc]", &toc); let body = format!( "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-next-id"], &config["wiki-server-url"], @@ -533,39 +554,24 @@ fn do_protocol( &config["matrix-homeserver-url"], &config["matrix-user-id"], &config["matrix-access-token"], - &config["matrix-room-id-for-short-messages"], - &config["matrix-room-id-for-long-messages"], + &config["matrix-room-id-1"], + &config["matrix-room-id-2"], is_dry_run(), ); // Send the matrix room message let human_date = plenum_day.format("%d.%m.%Y"); 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\ - 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-next-id"], &config["wiki-server-url"], &config["wiki-plenum-page"], &summary_or_toc ); - let full_long_message = format!( - "{}\n{}{}", - &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)?; + let full_message = format!("{}\n\n{}{}",&config["text-email-greeting"], &message, &config["text-email-signature"]); + matrix.send_message_to_two_rooms(&full_message)?; Ok(()) } diff --git a/src/matrix.rs b/src/matrix.rs index be122ab..05f10a8 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -1,5 +1,6 @@ use std::error::Error; use std::io::Read; +use lazy_static::lazy_static; use colored::Colorize; use regex::Regex; @@ -32,12 +33,12 @@ pub const CONFIG: CfgGroup<'static> = CfgGroup { description: "Access Token / \"password\" used for authenticating as the bot.", }, CfgField::Default { - key: "room-id-for-long-messages", + key: "room-id-1", default: "!someLongRoomIdentifier:matrix.org", description: "API Username associated with the bot account used for writing messages.", }, CfgField::Default { - key: "room-id-for-short-messages", + key: "room-id-2", default: "!someLongRoomIdentifier:matrix.org", description: "API Username associated with the bot account used for writing messages.", }, @@ -51,8 +52,8 @@ pub struct MatrixClient { is_dry_run: bool, client: Client, txn_id: u64, - room_id_for_short_messages: String, - room_id_for_long_messages: String, + room_id_1: String, + room_id_2: String, } #[derive(Serialize, Deserialize, Debug)] @@ -82,8 +83,8 @@ impl std::fmt::Debug for MatrixClient { impl MatrixClient { pub fn new( - homeserver_url: &str, user_id: &str, access_token: &str, room_id_for_short_messages: &str, - room_id_for_long_messages: &str, is_dry_run: bool, + homeserver_url: &str, user_id: &str, access_token: &str, room_id_1: &str, + room_id_2: &str, is_dry_run: bool, ) -> Self { Self { homeserver_url: homeserver_url.to_string(), @@ -92,8 +93,8 @@ impl MatrixClient { is_dry_run, client: Client::builder().cookie_store(true).build().unwrap(), 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_for_short_messages: room_id_for_short_messages.to_string(), + room_id_2: room_id_2.to_string(), + room_id_1: room_id_1.to_string(), } } fn request( @@ -102,7 +103,7 @@ impl MatrixClient { ) -> Result> { let client = reqwest::blocking::Client::new(); let url = format!("{}/_matrix/client{}", self.homeserver_url, endpoint); - print!("url: {}", url.yellow()); + verboseln!("url: {}\n", url.blue()); // Construct URL with query parameters let mut request = client.request(method, &url); @@ -176,41 +177,28 @@ impl MatrixClient { current } - pub fn pandoc_convert_md_to_html(markdown: String) -> Result> { - let (output, errors, status) = crate::pipe( - "pandoc", - &mut [ - "--from", "markdown-auto_identifiers", - "--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_text_to_html(markdown: String) -> Result> { + lazy_static! { + static ref MATRIX_BOLD: Regex = Regex::new(r"\*\*(.+?)\*\*").unwrap(); + static ref MATRIX_ITALIC: Regex = Regex::new(r"\*(.+?)\*").unwrap(); + static ref MATRIX_URL: Regex = Regex::new(r"(https?://\S+?)(?:[.,])?(?:\s|$)").unwrap(); } - } - - pub fn format_matrix_html(markdown: String) -> Result> { - let mut html = Self::pandoc_convert_md_to_html(markdown)?; - let url_regex = Regex::new(r"(https?://[^\s<]+)")?; - html = url_regex.replace_all(&html, r#"$1"#).to_string(); + let mut html = markdown; - html = html.replace("\n\n", "

"); + // Handle URLs first + html = MATRIX_URL.replace_all(&html, r"$1").to_string(); + + // Basic formatting + html = MATRIX_BOLD.replace_all(&html, r"$1").to_string(); + html = MATRIX_ITALIC.replace_all(&html, r"$1").to_string(); + + // Convert newlines to
html = html.replace("\n", "
"); - html = html.replace("**", ""); - html = html.replace("__", ""); - Ok(html) } - - pub fn pandoc_convert_text_to_md(markdown: String) -> Result> { + pub fn pandoc_convert_text_to_md(markdown: String) -> Result> { let (output, errors, status) = crate::pipe( "pandoc", &mut ["--from", "markdown-auto_identifiers", "--to", "html5"], @@ -227,14 +215,14 @@ impl MatrixClient { pub fn send_room_message( &mut self, room_id: &str, text: &str, ) -> Result> { - 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([ ("type", "m.room.message"), ("msgtype", "m.text"), ("body", text), ("format", "org.matrix.custom.html"), ("formatted_body", &formatted_text), - ("m.mentions", "{}"), + // ("m.mentions", "{}"), ]); 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, ) -> Result> { 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) } - pub fn send_short_and_long_messages_to_two_rooms( - &mut self, short_message: &str, long_message: &str, + pub fn send_message_to_two_rooms( + &mut self, message: &str ) -> Result<(), Box> { - self.send_room_message( &self.room_id_for_long_messages.clone(), &long_message,)?; - self.send_room_message( &self.room_id_for_short_messages.clone(), &short_message,)?; + self.send_room_message( &self.room_id_2.clone(), &message,)?; + self.send_room_message( &self.room_id_1.clone(), &message,)?; Ok(()) }