use base64::{Engine, engine::general_purpose::STANDARD};
use futures::future::join_all;
pub use mime;
use mime::Mime;
use regex::Regex;
#[cfg(feature = "reqwest")]
use reqwest::Client;
#[cfg(feature = "reqwest")]
pub use reqwest::header::{HeaderMap, HeaderValue};
use std::{str::FromStr, time::Duration};
#[derive(Clone)]
pub struct MatchedFiles {
pub index: usize,
pub length: usize,
pub mime_type: Option<Mime>,
pub base64: Option<String>,
}
#[cfg(feature = "reqwest")]
pub async fn get_file_base64s(
markdown: impl AsRef<str>,
regex: Regex,
guess_mime_type: fn(url: &str) -> Mime,
decide_download: fn(headers: &HeaderMap) -> bool,
timeout: Duration,
) -> Vec<MatchedFiles> {
let client = Client::builder().timeout(timeout).build().unwrap();
let mut tasks = Vec::new();
for file in regex.captures_iter(markdown.as_ref()) {
let capture = file.get(0).unwrap();
let url = file[1].to_string();
tasks.push((async |capture: regex::Match<'_>, url: String| {
let (mime_type, base64) = if url.starts_with("https://") || url.starts_with("http://") {
let response = client.get(&url).send().await;
match response {
Ok(response) if (decide_download)(response.headers()) => {
let mime_type = response
.headers()
.get("Content-Type")
.map(|mime| mime.to_str().ok())
.flatten()
.map(|mime| Mime::from_str(mime).ok())
.flatten();
let base64 = response
.bytes()
.await
.ok()
.map(|bytes| STANDARD.encode(bytes));
let mime_type = match base64 {
Some(_) => mime_type.or_else(|| Some(guess_mime_type(&url))),
None => None,
};
(mime_type, base64)
}
_ => (None, None),
}
} else {
let base64 = tokio::fs::read(url.clone())
.await
.ok()
.map(|bytes| STANDARD.encode(&bytes));
match base64 {
Some(base64) => (Some(guess_mime_type(&url)), Some(base64)),
None => (None, None),
}
};
MatchedFiles {
index: capture.start(),
length: capture.len(),
mime_type,
base64,
}
})(capture, url));
}
join_all(tasks).await
}