pub fn convert_text_to_hashiverse_html(text: &str) -> String {
let escaped = html_escape(text);
let chars: Vec<char> = escaped.chars().collect();
let len = chars.len();
let mut output = String::with_capacity(escaped.len() * 2);
let mut i = 0;
while i < len {
match chars[i] {
'#' => {
let start = i + 1;
let mut end = start;
while end < len && chars[end].is_alphanumeric() {
end += 1;
}
if end > start {
let hashtag_text: String = chars[start..end].iter().collect();
output.push_str(&convert_text_to_hashiverse_html_x_hashtag(&hashtag_text));
i = end;
} else {
output.push('#');
i += 1;
}
}
'@' => {
let start = i + 1;
let mut end = start;
while end < len && end - start < 64 && is_hex_char(chars[end]) {
end += 1;
}
let hex_len = end - start;
if hex_len == 64 && (end >= len || !is_hex_char(chars[end])) {
let hex_string: String = chars[start..end].iter().collect();
output.push_str(&convert_text_to_hashiverse_html_x_mention(&hex_string));
i = end;
} else {
output.push('@');
i += 1;
}
}
'\n' => {
output.push_str("<br>");
i += 1;
}
'\r' => {
i += 1;
}
c => {
output.push(c);
i += 1;
}
}
}
output
}
pub fn convert_text_to_hashiverse_html_x_hashtag(hashtag: &str) -> String {
let stripped = hashtag.strip_prefix('#').unwrap_or(hashtag);
if stripped.is_empty() || !stripped.chars().all(char::is_alphanumeric) {
return hashtag.to_string();
}
let stripped_lower = stripped.to_lowercase();
format!(
"<hashtag hashtag=\"{}\"><span class=\"plugin-hashtag-left\">#</span><span class=\"plugin-hashtag-right\">{}</span></hashtag>",
stripped_lower, stripped,
)
}
pub fn convert_text_to_hashiverse_html_x_mention(client_id: &str) -> String {
format!("<mention client_id=\"{}\"></mention>", client_id)
}
pub fn convert_text_to_hashiverse_html_x_url_preview(
title: &str,
description: &str,
image_url: &str,
url: &str,
) -> String {
let domain = extract_host_or_url(url);
let mut out = String::with_capacity(512);
out.push_str("<div class=\"plugin-urlpreview-card\">");
if !image_url.is_empty() {
out.push_str("<div class=\"plugin-urlpreview-card-image-container\">");
out.push_str(&format!(
"<a class=\"plugin-urlpreview-card-image-link\" href=\"{}\" rel=\"noopener noreferrer nofollow\"><img src=\"{}\" alt=\"\" class=\"plugin-urlpreview-card-image unblur-image\"></a>",
html_escape(url),
html_escape(image_url),
));
out.push_str(&format!(
"<div class=\"plugin-urlpreview-card-domain\">{}</div>",
html_escape(domain),
));
out.push_str("</div>");
}
out.push_str("<div class=\"plugin-urlpreview-card-inner\">");
if image_url.is_empty() {
out.push_str(&format!(
"<div class=\"plugin-urlpreview-card-domain\">{}</div>",
html_escape(domain),
));
}
out.push_str(&format!(
"<a class=\"plugin-urlpreview-card-title\" href=\"{}\" rel=\"noopener noreferrer nofollow\">{}</a>",
html_escape(url),
html_escape(title),
));
if !description.is_empty() {
out.push_str(&format!(
"<div class=\"plugin-urlpreview-card-description\">{}</div>",
html_escape(description),
));
}
out.push_str("</div>");
out.push_str("</div>");
out
}
fn extract_host_or_url(url: &str) -> &str {
match url.split_once("://") {
Some((_, after)) => after
.split(['/', '?', '#'])
.next()
.filter(|s| !s.is_empty())
.unwrap_or(url),
None => url,
}
}
fn html_escape(text: &str) -> String {
let mut escaped = String::with_capacity(11 * text.len() / 10);
for c in text.chars() {
match c {
'&' => escaped.push_str("&"),
'<' => escaped.push_str("<"),
'>' => escaped.push_str(">"),
'"' => escaped.push_str("""),
other => escaped.push(other),
}
}
escaped
}
fn is_hex_char(c: char) -> bool {
c.is_ascii_hexdigit()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hashtag_at_start() {
let result = convert_text_to_hashiverse_html("#rust is great");
assert!(result.contains("<hashtag hashtag=\"rust\">"));
assert!(result.contains("<span class=\"plugin-hashtag-right\">rust</span>"));
assert!(result.ends_with(" is great"));
}
#[test]
fn test_hashtag_at_end() {
let result = convert_text_to_hashiverse_html("hello #rust");
assert!(result.starts_with("hello "));
assert!(result.contains("<hashtag hashtag=\"rust\">"));
}
#[test]
fn test_hashtag_in_middle() {
let result = convert_text_to_hashiverse_html("I love #rust programming");
assert!(result.contains("<hashtag hashtag=\"rust\">"));
assert!(result.contains(" programming"));
}
#[test]
fn test_multiple_hashtags() {
let result = convert_text_to_hashiverse_html("#rust and #golang");
assert!(result.contains("<hashtag hashtag=\"rust\">"));
assert!(result.contains("<hashtag hashtag=\"golang\">"));
}
#[test]
fn test_adjacent_hashtags() {
let result = convert_text_to_hashiverse_html("#rust#golang");
assert!(result.contains("<hashtag hashtag=\"rust\">"));
assert!(result.contains("<hashtag hashtag=\"golang\">"));
}
#[test]
fn test_hashtag_case_lowered_in_attribute() {
let result = convert_text_to_hashiverse_html("#RuStLang");
assert!(result.contains("hashtag=\"rustlang\""));
assert!(result.contains("<span class=\"plugin-hashtag-right\">RuStLang</span>"));
}
#[test]
fn test_bare_hash_no_alphanumeric() {
assert_eq!(convert_text_to_hashiverse_html("# alone"), "# alone");
}
#[test]
fn test_hash_at_end_of_string() {
assert_eq!(convert_text_to_hashiverse_html("test #"), "test #");
}
#[test]
fn test_unicode_hashtag() {
let result = convert_text_to_hashiverse_html("#日本語");
assert!(result.contains("hashtag=\"日本語\""));
assert!(result.contains("<span class=\"plugin-hashtag-right\">日本語</span>"));
}
#[test]
fn test_hashtag_with_numbers() {
let result = convert_text_to_hashiverse_html("#web3");
assert!(result.contains("hashtag=\"web3\""));
}
#[test]
fn test_hashtag_terminated_by_punctuation() {
let result = convert_text_to_hashiverse_html("#rust, nice");
assert!(result.contains("<hashtag hashtag=\"rust\">"));
assert!(result.contains("</hashtag>, nice"));
}
#[test]
fn test_valid_mention() {
let hex_id = "a".repeat(64);
let input = format!("hello @{} world", hex_id);
let result = convert_text_to_hashiverse_html(&input);
assert!(result.contains(&format!("<mention client_id=\"{}\"></mention>", hex_id)));
assert!(result.starts_with("hello "));
assert!(result.ends_with(" world"));
}
#[test]
fn test_mention_mixed_case_hex() {
let hex_id = "aAbBcCdDeEfF0011223344556677889900112233445566778899aAbBcCdDeEfF";
assert_eq!(hex_id.len(), 64);
let input = format!("@{}", hex_id);
let result = convert_text_to_hashiverse_html(&input);
assert!(result.contains(&format!("<mention client_id=\"{}\"></mention>", hex_id)));
}
#[test]
fn test_mention_too_short() {
let result = convert_text_to_hashiverse_html("@abcdef");
assert_eq!(result, "@abcdef");
assert!(!result.contains("<mention"));
}
#[test]
fn test_mention_non_hex_after_at() {
let result = convert_text_to_hashiverse_html("@hello");
assert_eq!(result, "@hello");
}
#[test]
fn test_bare_at() {
assert_eq!(convert_text_to_hashiverse_html("@"), "@");
}
#[test]
fn test_at_end_of_string() {
assert_eq!(convert_text_to_hashiverse_html("test @"), "test @");
}
#[test]
fn test_mention_65_hex_chars_not_matched() {
let hex_65 = "a".repeat(65);
let input = format!("@{}", hex_65);
let result = convert_text_to_hashiverse_html(&input);
assert!(!result.contains("<mention"));
}
#[test]
fn test_mention_64_hex_then_non_hex() {
let hex_id = "b".repeat(64);
let input = format!("@{}xyz", hex_id);
let result = convert_text_to_hashiverse_html(&input);
assert!(result.contains(&format!("<mention client_id=\"{}\"></mention>", hex_id)));
assert!(result.ends_with("xyz"));
}
#[test]
fn test_html_injection_escaped() {
let result = convert_text_to_hashiverse_html("<script>alert(1)</script>");
assert!(result.contains("<script>"));
assert!(!result.contains("<script>"));
}
#[test]
fn test_ampersand_escaped() {
let result = convert_text_to_hashiverse_html("AT&T");
assert_eq!(result, "AT&T");
}
#[test]
fn test_quotes_escaped() {
let result = convert_text_to_hashiverse_html("he said \"hello\"");
assert!(result.contains("""));
}
#[test]
fn test_newline_to_br() {
let result = convert_text_to_hashiverse_html("line1\nline2");
assert_eq!(result, "line1<br>line2");
}
#[test]
fn test_crlf_to_br() {
let result = convert_text_to_hashiverse_html("line1\r\nline2");
assert_eq!(result, "line1<br>line2");
}
#[test]
fn test_bare_cr_skipped() {
let result = convert_text_to_hashiverse_html("line1\rline2");
assert_eq!(result, "line1line2");
}
#[test]
fn test_combined_post() {
let hex_id = "c".repeat(64);
let input = format!("Hello #hashiverse from @{}!\nGreat to be here.", hex_id);
let result = convert_text_to_hashiverse_html(&input);
assert!(result.contains("<hashtag hashtag=\"hashiverse\">"));
assert!(result.contains(&format!("<mention client_id=\"{}\"></mention>", hex_id)));
assert!(result.contains("<br>"));
assert!(result.contains("Great to be here."));
}
#[test]
fn test_empty_string() {
assert_eq!(convert_text_to_hashiverse_html(""), "");
}
#[test]
fn test_plain_text_no_specials() {
assert_eq!(convert_text_to_hashiverse_html("just a normal post"), "just a normal post");
}
#[test]
fn test_round_trip_hashtag_extraction() {
let result = convert_text_to_hashiverse_html("I love #Rust and #golang");
let html = scraper::Html::parse_fragment(&result);
let selector = scraper::Selector::parse("hashtag").unwrap();
let hashtags: Vec<&str> = html.select(&selector)
.filter_map(|el| el.attr("hashtag"))
.collect();
assert_eq!(hashtags, vec!["rust", "golang"]);
}
#[test]
fn test_round_trip_mention_extraction() {
let hex_id = "d".repeat(64);
let result = convert_text_to_hashiverse_html(&format!("hello @{}", hex_id));
let html = scraper::Html::parse_fragment(&result);
let selector = scraper::Selector::parse("mention").unwrap();
let client_ids: Vec<&str> = html.select(&selector)
.filter_map(|el| el.attr("client_id"))
.collect();
assert_eq!(client_ids, vec![hex_id.as_str()]);
}
#[test]
fn test_round_trip_combined() {
let hex_id = "e".repeat(64);
let input = format!("#hashiverse post by @{} about #Rust", hex_id);
let result = convert_text_to_hashiverse_html(&input);
let html = scraper::Html::parse_fragment(&result);
let hashtag_selector = scraper::Selector::parse("hashtag").unwrap();
let hashtags: Vec<&str> = html.select(&hashtag_selector)
.filter_map(|el| el.attr("hashtag"))
.collect();
assert_eq!(hashtags, vec!["hashiverse", "rust"]);
let mention_selector = scraper::Selector::parse("mention").unwrap();
let client_ids: Vec<&str> = html.select(&mention_selector)
.filter_map(|el| el.attr("client_id"))
.collect();
assert_eq!(client_ids, vec![hex_id.as_str()]);
}
#[test]
fn test_x_hashtag_lowercases_attribute_preserves_span_text() {
let result = convert_text_to_hashiverse_html_x_hashtag("RuStLang");
assert!(result.contains("hashtag=\"rustlang\""));
assert!(result.contains("<span class=\"plugin-hashtag-right\">RuStLang</span>"));
assert!(result.contains("<span class=\"plugin-hashtag-left\">#</span>"));
}
#[test]
fn test_x_hashtag_handles_unicode() {
let result = convert_text_to_hashiverse_html_x_hashtag("日本語");
assert!(result.contains("hashtag=\"日本語\""));
assert!(result.contains("<span class=\"plugin-hashtag-right\">日本語</span>"));
}
#[test]
fn test_x_hashtag_non_alphanumeric_returns_input_untouched() {
for bad in ["a<b", "a\"b", "a b", "a&b", "##rust", "#a<b"] {
assert_eq!(convert_text_to_hashiverse_html_x_hashtag(bad), bad);
}
}
#[test]
fn test_x_hashtag_empty_returns_empty() {
assert_eq!(convert_text_to_hashiverse_html_x_hashtag(""), "");
}
#[test]
fn test_x_hashtag_lone_hash_returns_lone_hash() {
assert_eq!(convert_text_to_hashiverse_html_x_hashtag("#"), "#");
}
#[test]
fn test_x_hashtag_strips_leading_hash_if_provided() {
let with_hash = convert_text_to_hashiverse_html_x_hashtag("#rust");
let without_hash = convert_text_to_hashiverse_html_x_hashtag("rust");
assert_eq!(with_hash, without_hash);
assert!(with_hash.contains("hashtag=\"rust\""));
assert!(with_hash.contains("<span class=\"plugin-hashtag-right\">rust</span>"));
}
#[test]
fn test_x_hashtag_no_op_does_not_emit_hashtag_element() {
for bad in ["a<b", "a\"b", "a b", "", "##rust", "#a<b"] {
let result = convert_text_to_hashiverse_html_x_hashtag(bad);
assert!(!result.contains("<hashtag"), "unexpected element for {bad:?}: {result:?}");
assert!(!result.contains("plugin-hashtag-left"), "unexpected span for {bad:?}: {result:?}");
}
}
#[test]
fn test_x_mention_emits_64hex_client_id() {
let hex_id = "f".repeat(64);
let result = convert_text_to_hashiverse_html_x_mention(&hex_id);
assert_eq!(result, format!("<mention client_id=\"{}\"></mention>", hex_id));
}
#[test]
fn test_x_url_preview_with_image_renders_image_container() {
let result = convert_text_to_hashiverse_html_x_url_preview(
"Title",
"Desc",
"https://img.example/x.png",
"https://example.com/path",
);
assert!(result.starts_with("<div class=\"plugin-urlpreview-card\"><div class=\"plugin-urlpreview-card-image-container\">"));
assert!(result.contains(
"<a class=\"plugin-urlpreview-card-image-link\" href=\"https://example.com/path\" rel=\"noopener noreferrer nofollow\"><img src=\"https://img.example/x.png\" alt=\"\" class=\"plugin-urlpreview-card-image unblur-image\"></a>"
));
assert!(result.contains("<div class=\"plugin-urlpreview-card-domain\">example.com</div>"));
assert!(result.contains("<a class=\"plugin-urlpreview-card-title\" href=\"https://example.com/path\" rel=\"noopener noreferrer nofollow\">Title</a>"));
assert!(result.contains("<div class=\"plugin-urlpreview-card-description\">Desc</div>"));
}
#[test]
fn test_x_url_preview_image_link_uses_same_href_as_title() {
let url = "https://example.com/?a=1&b=2";
let result = convert_text_to_hashiverse_html_x_url_preview("Title", "", "https://img.example/x.png", url);
assert!(result.contains(
"<a class=\"plugin-urlpreview-card-image-link\" href=\"https://example.com/?a=1&b=2\" rel=\"noopener noreferrer nofollow\"><img"
));
assert!(result.contains(
"<a class=\"plugin-urlpreview-card-title\" href=\"https://example.com/?a=1&b=2\" rel=\"noopener noreferrer nofollow\">Title</a>"
));
}
#[test]
fn test_x_url_preview_without_image_moves_domain_into_inner() {
let result = convert_text_to_hashiverse_html_x_url_preview(
"Title",
"Desc",
"",
"https://example.com/path",
);
assert!(!result.contains("plugin-urlpreview-card-image-container"));
assert!(!result.contains("<img "));
let inner_pos = result.find("plugin-urlpreview-card-inner").unwrap();
let domain_pos = result.find("plugin-urlpreview-card-domain").unwrap();
let title_pos = result.find("plugin-urlpreview-card-title").unwrap();
assert!(inner_pos < domain_pos && domain_pos < title_pos);
}
#[test]
fn test_x_url_preview_omits_description_when_blank() {
let result = convert_text_to_hashiverse_html_x_url_preview(
"Title",
"",
"https://img.example/x.png",
"https://example.com/",
);
assert!(!result.contains("plugin-urlpreview-card-description"));
}
#[test]
fn test_x_url_preview_extracts_domain_from_https_url() {
let result = convert_text_to_hashiverse_html_x_url_preview(
"T", "", "", "https://sub.example.com:8443/path?q=1#frag",
);
assert!(result.contains("<div class=\"plugin-urlpreview-card-domain\">sub.example.com:8443</div>"));
}
#[test]
fn test_x_url_preview_falls_back_to_full_url_when_no_scheme() {
let result = convert_text_to_hashiverse_html_x_url_preview("T", "", "", "not-a-url");
assert!(result.contains("<div class=\"plugin-urlpreview-card-domain\">not-a-url</div>"));
}
#[test]
fn test_x_url_preview_html_escapes_attribute_and_text_values() {
let result = convert_text_to_hashiverse_html_x_url_preview(
"Title with <script> & \"quotes\"",
"Desc with <html> & \"chars\"",
"https://i/x?a=1&b=2",
"https://example.com/?q=1&r=2",
);
assert!(result.contains(">Title with <script> & "quotes"</a>"));
assert!(result.contains(">Desc with <html> & "chars"</div>"));
assert!(result.contains("src=\"https://i/x?a=1&b=2\""));
assert!(result.contains("href=\"https://example.com/?q=1&r=2\""));
}
#[test]
fn test_existing_convert_text_to_hashiverse_html_unchanged_after_refactor() {
let hex = "a".repeat(64);
let input = format!("Hi #Rust @{} bye", hex);
let result = convert_text_to_hashiverse_html(&input);
assert_eq!(
result,
format!(
"Hi <hashtag hashtag=\"rust\"><span class=\"plugin-hashtag-left\">#</span><span class=\"plugin-hashtag-right\">Rust</span></hashtag> <mention client_id=\"{}\"></mention> bye",
hex
)
);
}
}