diff --git a/server/src/chat/util.rs b/server/src/chat/util.rs index 81b24a88..cfe1bdf5 100644 --- a/server/src/chat/util.rs +++ b/server/src/chat/util.rs @@ -49,9 +49,30 @@ pub fn strip_prefix_ci<'a>(text: &'a str, prefix: &str) -> Option<&'a str> { /// - `DisplayName: rest` → `rest` (Element tab-completion inserts a colon) /// - `DisplayName, rest` → `rest` (Element tab-completion may insert a comma) /// - `DisplayName ⚡️: rest` → `rest` (display name with emoji) +/// - `[DisplayName](https://matrix.to/#/@user:server) rest` → `rest` (Element mention pill) pub fn strip_bot_mention<'a>(message: &'a str, bot_name: &str, bot_user_id: &str) -> &'a str { let trimmed = message.trim(); + // Try Element Markdown mention pill format: + // "[DisplayName](https://matrix.to/#/@user:server) rest" + if trimmed.starts_with('[') { + if let Some(after_label) = trimmed.find("](https://matrix.to/#/") { + let url_start = after_label + 2; // skip "](" + let url_content = &trimmed[url_start..]; // "https://matrix.to/#/@user:server) rest" + if let Some(close_paren) = url_content.find(')') { + let url = &url_content[..close_paren]; // "https://matrix.to/#/@user:server" + let matrix_prefix = "https://matrix.to/#/"; + if url.starts_with(matrix_prefix) { + let mentioned_id = &url[matrix_prefix.len()..]; + if mentioned_id.eq_ignore_ascii_case(bot_user_id) { + let rest = &url_content[close_paren + 1..]; + return strip_mention_separator(rest); + } + } + } + } + } + // Try full Matrix user ID (e.g. "@timmy:homeserver.local") if let Some(rest) = strip_prefix_ci(trimmed, bot_user_id) { return strip_mention_separator(rest); @@ -393,6 +414,53 @@ mod tests { assert_eq!(rest, "status"); } + #[test] + fn strip_mention_element_markdown_pill_format() { + // Element sends "[DisplayName](https://matrix.to/#/@user:server) command" + // when a user uses the @ autocomplete mention pill. + let rest = strip_bot_mention( + "[Timmy](https://matrix.to/#/@timmy:homeserver.local) status", + "Timmy", + "@timmy:homeserver.local", + ); + assert_eq!(rest, "status"); + } + + #[test] + fn strip_mention_element_markdown_pill_with_emoji_display_name() { + let rest = strip_bot_mention( + "[timmy ⚡️](https://matrix.to/#/@timmy:homeserver.local) ambient on", + "timmy ⚡️", + "@timmy:homeserver.local", + ); + assert_eq!(rest, "ambient on"); + } + + #[test] + fn strip_mention_element_markdown_pill_wrong_user_id_no_strip() { + // Pill for a different user should not be stripped. + let rest = strip_bot_mention( + "[Other](https://matrix.to/#/@other:homeserver.local) status", + "Timmy", + "@timmy:homeserver.local", + ); + assert_eq!( + rest, + "[Other](https://matrix.to/#/@other:homeserver.local) status" + ); + } + + #[test] + fn strip_mention_element_markdown_pill_no_trailing_command() { + // Pill with no command after it returns empty string (handled by callers). + let rest = strip_bot_mention( + "[Timmy](https://matrix.to/#/@timmy:homeserver.local)", + "Timmy", + "@timmy:homeserver.local", + ); + assert_eq!(rest, ""); + } + // -- drain_complete_paragraphs ------------------------------------------ #[test]