1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3pub enum MediaKind {
4 Audio,
5 Image,
6 Video,
7 Unknown,
8}
9
10#[derive(Debug, Clone)]
12pub struct MediaAttachment {
13 pub file_name: String,
15 pub data: Vec<u8>,
17 pub mime_type: Option<String>,
19}
20
21impl MediaAttachment {
22 pub fn from_file(path: &str) -> anyhow::Result<Self> {
36 let p = std::path::Path::new(path);
37 let data = std::fs::read(p)?;
38 let file_name = p
39 .file_name()
40 .and_then(|n| n.to_str())
41 .unwrap_or("attachment")
42 .to_string();
43 let mime_type = match p.extension().and_then(|e| e.to_str()) {
44 Some("pdf") => Some("application/pdf".to_string()),
45 Some("xlsx") => Some(
46 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet".to_string(),
47 ),
48 Some("docx") => Some(
49 "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
50 .to_string(),
51 ),
52 Some("csv") => Some("text/csv".to_string()),
53 Some("png") => Some("image/png".to_string()),
54 Some("jpg") | Some("jpeg") => Some("image/jpeg".to_string()),
55 Some("txt") => Some("text/plain".to_string()),
56 _ => Some("application/octet-stream".to_string()),
57 };
58 Ok(Self {
59 file_name,
60 data,
61 mime_type,
62 })
63 }
64
65 pub fn kind(&self) -> MediaKind {
67 if let Some(ref mime) = self.mime_type {
69 let lower = mime.to_ascii_lowercase();
70 if lower.starts_with("audio/") {
71 return MediaKind::Audio;
72 }
73 if lower.starts_with("image/") {
74 return MediaKind::Image;
75 }
76 if lower.starts_with("video/") {
77 return MediaKind::Video;
78 }
79 }
80
81 let ext = self
83 .file_name
84 .rsplit_once('.')
85 .map(|(_, e)| e.to_ascii_lowercase())
86 .unwrap_or_default();
87
88 match ext.as_str() {
89 "flac" | "mp3" | "mpeg" | "mpga" | "m4a" | "ogg" | "oga" | "opus" | "wav" | "webm" => {
90 MediaKind::Audio
91 }
92 "png" | "jpg" | "jpeg" | "gif" | "bmp" | "webp" | "heic" | "tiff" | "svg" => {
93 MediaKind::Image
94 }
95 "mp4" | "mkv" | "avi" | "mov" | "wmv" | "flv" => MediaKind::Video,
96 _ => MediaKind::Unknown,
97 }
98 }
99}