use base64::{Engine, engine::general_purpose::STANDARD};
#[derive(Clone, Debug)]
pub struct PastedImage {
pub media_type: String,
pub base64_data: String,
}
pub const MAX_PASTED_IMAGES_PER_MESSAGE: usize = 20;
pub fn marker_for_index(n: usize) -> Option<char> {
if n < MAX_PASTED_IMAGES_PER_MESSAGE {
char::from_u32(0x2460 + n as u32)
} else {
None
}
}
fn index_from_char(c: char) -> Option<usize> {
let val = c as u32;
if (0x2460..=0x2473).contains(&val) {
Some((val - 0x2460) as usize)
} else {
None
}
}
pub fn strip_paste_markers(input: &str) -> (String, Vec<usize>) {
let mut indices = Vec::new();
let mut cleaned = String::new();
for c in input.chars() {
if let Some(idx) = index_from_char(c) {
indices.push(idx);
} else {
cleaned.push(c);
}
}
(cleaned.trim().to_string(), indices)
}
const MAX_CLIPBOARD_IMAGE_BYTES: usize = 20 * 1024 * 1024;
pub fn get_clipboard_image() -> Option<PastedImage> {
let mut clipboard = arboard::Clipboard::new().ok()?;
let image = clipboard.get_image().ok()?;
if image.width == 0 || image.height == 0 {
return None;
}
let mut buf = Vec::new();
{
let mut encoder = png::Encoder::new(&mut buf, image.width as u32, image.height as u32);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header().ok()?;
writer.write_image_data(&image.bytes).ok()?;
}
let binary_cap = MAX_CLIPBOARD_IMAGE_BYTES * 3 / 4;
if buf.len() > binary_cap {
tracing::warn!(
bytes = buf.len(),
limit = binary_cap,
"dropping oversized clipboard image"
);
return None;
}
let base64_data = STANDARD.encode(&buf);
if base64_data.len() > MAX_CLIPBOARD_IMAGE_BYTES {
tracing::warn!(
bytes = base64_data.len(),
limit = MAX_CLIPBOARD_IMAGE_BYTES,
"dropping clipboard image whose base64 form exceeds the API limit"
);
return None;
}
Some(PastedImage {
media_type: "image/png".to_string(),
base64_data,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn marker_for_first_index_returns_circled_one() {
assert_eq!(marker_for_index(0), Some('\u{2460}'));
}
#[test]
fn marker_for_last_supported_index_returns_circled_twenty() {
assert_eq!(
marker_for_index(MAX_PASTED_IMAGES_PER_MESSAGE - 1),
Some('\u{2473}')
);
}
#[test]
fn marker_past_supported_range_returns_none() {
assert_eq!(marker_for_index(MAX_PASTED_IMAGES_PER_MESSAGE), None);
assert_eq!(marker_for_index(100), None);
}
#[test]
fn strip_paste_markers_round_trips_indices_and_text() {
let input = format!(
"look at {} and {}",
marker_for_index(0).unwrap(),
marker_for_index(2).unwrap()
);
let (text, indices) = strip_paste_markers(&input);
assert_eq!(text, "look at and");
assert_eq!(indices, vec![0, 2]);
}
}