512369f93e 2026-08-01 1: //! Utility functions and types for the application.
512369f93e 2026-08-01 2:
f5ed284f8c 2025-06-21 3: use crate::Cursor;
f5ed284f8c 2025-06-21 4:
997fb1cff4 2026-08-01 5: use std::{
997fb1cff4 2026-08-01 6: borrow::Cow,
997fb1cff4 2026-08-01 7: sync::LazyLock,
997fb1cff4 2026-08-01 8: };
85fa6bddaa 2026-01-18 9:
85fa6bddaa 2026-01-18 10: use html_escape::encode_text;
bf99298edf 2026-07-31 11: use regex::{
bf99298edf 2026-07-31 12: Regex,
bf99298edf 2026-07-31 13: RegexBuilder,
bf99298edf 2026-07-31 14: };
0f47e23e21 2026-01-12 15: use stacked_errors::{
0f47e23e21 2026-01-12 16: bail,
0f47e23e21 2026-01-12 17: Result,
0f47e23e21 2026-01-12 18: };
0f47e23e21 2026-01-12 19:
997fb1cff4 2026-08-01 20: pub static RE_DOMAIN: LazyLock<Regex> = LazyLock::new(|| {
997fb1cff4 2026-08-01 21: Regex::new(r"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$").expect("Invalid domain regex")
997fb1cff4 2026-08-01 22: });
997fb1cff4 2026-08-01 23: pub static RE_CLOSING: LazyLock<Regex> = LazyLock::new(|| {
997fb1cff4 2026-08-01 24: RegexBuilder::new(r"</[ \t]*(pre|code)[ \t]*>")
997fb1cff4 2026-08-01 25: .case_insensitive(true).build().expect("Invalid closing tag regex")
997fb1cff4 2026-08-01 26: });
0f47e23e21 2026-01-12 27:
3c858ed7c4 2026-07-31 28: /// Stores binary attachment data and metadata for Telegram messages.
3c858ed7c4 2026-07-31 29: /// The data is wrapped in a `Cursor<Vec<u8>>` for efficient streaming,
3c858ed7c4 2026-07-31 30: /// while `name` holds the filename or display name of the attachment.
f5ed284f8c 2025-06-21 31: #[derive(Debug)]
f5ed284f8c 2025-06-21 32: pub struct Attachment {
f5ed284f8c 2025-06-21 33: pub data: Cursor<Vec<u8>>,
f5ed284f8c 2025-06-21 34: pub name: String,
0f47e23e21 2026-01-12 35: }
0f47e23e21 2026-01-12 36:
512369f93e 2026-08-01 37: /// Validates text to ensure it doesn't break Telegram's preformatted blocks.
512369f93e 2026-08-01 38: ///
512369f93e 2026-08-01 39: /// Escapes HTML special characters to prevent injection.
512369f93e 2026-08-01 40: ///
512369f93e 2026-08-01 41: /// # Arguments
512369f93e 2026-08-01 42: /// * `text` - Text to validate and escape.
512369f93e 2026-08-01 43: ///
512369f93e 2026-08-01 44: /// # Returns
512369f93e 2026-08-01 45: /// * `Result<Cow<'a, str>>` - Escaped text or error if invalid.
512369f93e 2026-08-01 46: ///
512369f93e 2026-08-01 47: /// # Errors
512369f93e 2026-08-01 48: /// Returns an error if the text contains Telegram closing tags (`</pre>`, `</code>`).
3c858ed7c4 2026-07-31 49: pub fn validate <'a>(text: &'a str) -> Result<Cow<'a, str>> {
dafeec0481 2026-01-18 50: if RE_CLOSING.is_match(text) {
dafeec0481 2026-01-18 51: bail!("Telegram closing tag found.");
dafeec0481 2026-01-18 52: } else {
85fa6bddaa 2026-01-18 53: Ok(encode_text(text))
dafeec0481 2026-01-18 54: }
f5ed284f8c 2025-06-21 55: }