use super::args::*;
use crate::formats::{
AzwHandler, CbzHandler, EpubHandler, Fb2Handler, MobiHandler, PdfHandler, TxtHandler,
};
use crate::traits::{EbookOperator, EbookReader, EbookWriter};
use crate::{image_optimizer::OptimizationOptions, Converter, Metadata};
use base64::Engine;
use rmcp::model::{CallToolResult, Content};
use std::path::PathBuf;
pub(super) async fn read_ebook(args: ReadEbookArgs) -> CallToolResult {
let path = args.path;
let extract_metadata = args.extract_metadata;
let extract_toc = args.extract_toc;
let path_buf = PathBuf::from(&path);
let format = match crate::utils::detect_format(&path_buf) {
Ok(f) => f,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to detect format: {e}"
))]);
}
};
let text = match format.as_str() {
"epub" => {
let mut handler = EpubHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!("Failed to read EPUB: {e}"))]);
}
if extract_metadata {
match handler.get_metadata() {
Ok(m) => serde_json::to_string_pretty(&m).unwrap(),
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get metadata: {e}"
))]);
}
}
} else if extract_toc {
match handler.get_toc() {
Ok(toc) => format!(
"Table of Contents:\n{}",
toc.iter()
.map(|e| format!("{}{}", " ".repeat(e.level - 1), e.title))
.collect::<Vec<_>>()
.join("\n")
),
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get TOC: {e}"
))]);
}
}
} else {
match handler.get_content() {
Ok(c) => c,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get content: {e}"
))]);
}
}
}
}
"cbz" => {
let mut handler = CbzHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!("Failed to read CBZ: {e}"))]);
}
if extract_metadata {
match handler.get_metadata() {
Ok(m) => serde_json::to_string_pretty(&m).unwrap(),
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get metadata: {e}"
))]);
}
}
} else if extract_toc {
match handler.get_toc() {
Ok(toc) => format!(
"Table of Contents:\n{}",
toc.iter()
.map(|e| {
format!(
"{}{}",
" ".repeat(e.level.saturating_sub(1)),
e.title
)
})
.collect::<Vec<_>>()
.join("\n")
),
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get TOC: {e}"
))]);
}
}
} else {
match handler.get_content() {
Ok(c) => c,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get content: {e}"
))]);
}
}
}
}
"txt" => {
let mut handler = TxtHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!("Failed to read TXT: {e}"))]);
}
if extract_metadata {
match handler.get_metadata() {
Ok(m) => serde_json::to_string_pretty(&m).unwrap(),
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get metadata: {e}"
))]);
}
}
} else if extract_toc {
match handler.get_toc() {
Ok(toc) => format!(
"Table of Contents:\n{}",
toc.iter()
.map(|e| e.title.clone())
.collect::<Vec<_>>()
.join("\n")
),
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get TOC: {e}"
))]);
}
}
} else {
match handler.get_content() {
Ok(c) => c,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get content: {e}"
))]);
}
}
}
}
"pdf" => {
let mut handler = PdfHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!("Failed to read PDF: {e}"))]);
}
if extract_metadata {
match handler.get_metadata() {
Ok(m) => serde_json::to_string_pretty(&m).unwrap(),
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get metadata: {e}"
))]);
}
}
} else {
match handler.get_content() {
Ok(c) => c,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get content: {e}"
))]);
}
}
}
}
"mobi" => {
let mut handler = MobiHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!("Failed to read MOBI: {e}"))]);
}
if extract_metadata {
match handler.get_metadata() {
Ok(m) => serde_json::to_string_pretty(&m).unwrap(),
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get metadata: {e}"
))]);
}
}
} else if extract_toc {
match handler.get_toc() {
Ok(toc) => format!(
"Table of Contents:\n{}",
toc.iter()
.map(|e| {
format!(
"{}{}",
" ".repeat(e.level.saturating_sub(1)),
e.title
)
})
.collect::<Vec<_>>()
.join("\n")
),
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get TOC: {e}"
))]);
}
}
} else {
match handler.get_content() {
Ok(c) => c,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get content: {e}"
))]);
}
}
}
}
"azw" => {
let mut handler = AzwHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!("Failed to read AZW: {e}"))]);
}
if extract_metadata {
match handler.get_metadata() {
Ok(m) => serde_json::to_string_pretty(&m).unwrap(),
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get metadata: {e}"
))]);
}
}
} else if extract_toc {
match handler.get_toc() {
Ok(toc) => format!(
"Table of Contents:\n{}",
toc.iter()
.map(|e| {
format!(
"{}{}",
" ".repeat(e.level.saturating_sub(1)),
e.title
)
})
.collect::<Vec<_>>()
.join("\n")
),
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get TOC: {e}"
))]);
}
}
} else {
match handler.get_content() {
Ok(c) => c,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get content: {e}"
))]);
}
}
}
}
"fb2" => {
let mut handler = Fb2Handler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!("Failed to read FB2: {e}"))]);
}
if extract_metadata {
match handler.get_metadata() {
Ok(m) => serde_json::to_string_pretty(&m).unwrap(),
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get metadata: {e}"
))]);
}
}
} else if extract_toc {
match handler.get_toc() {
Ok(toc) => format!(
"Table of Contents:\n{}",
toc.iter()
.map(|e| {
format!(
"{}{}",
" ".repeat(e.level.saturating_sub(1)),
e.title
)
})
.collect::<Vec<_>>()
.join("\n")
),
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get TOC: {e}"
))]);
}
}
} else {
match handler.get_content() {
Ok(c) => c,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get content: {e}"
))]);
}
}
}
}
_ => {
return CallToolResult::error(vec![Content::text(format!(
"Unsupported format: {format}"
))]);
}
};
CallToolResult::success(vec![Content::text(text)])
}
pub(super) async fn write_ebook(args: WriteEbookArgs) -> CallToolResult {
let path = args.path;
let format = args.format;
let title = args.title.as_deref();
let author = args.author.as_deref();
let content = args.content.as_deref().unwrap_or("");
let path_buf = PathBuf::from(&path);
let mut metadata = Metadata::new();
if let Some(t) = title {
metadata.title = Some(t.to_string());
}
if let Some(a) = author {
metadata.author = Some(a.to_string());
}
let res = match format.as_str() {
"txt" => {
let mut handler = TxtHandler::new();
handler
.set_metadata(metadata)
.and_then(|_| handler.set_content(content))
.and_then(|_| handler.write_to_file(&path_buf))
}
"epub" => {
let mut handler = EpubHandler::new();
handler
.set_metadata(metadata)
.and_then(|_| handler.set_content(content))
.and_then(|_| handler.write_to_file(&path_buf))
}
"pdf" => {
let mut handler = PdfHandler::new();
handler
.set_metadata(metadata)
.and_then(|_| handler.set_content(content))
.and_then(|_| handler.write_to_file(&path_buf))
}
"mobi" => {
let mut handler = MobiHandler::new();
handler
.set_metadata(metadata)
.and_then(|_| handler.set_content(content))
.and_then(|_| handler.write_to_file(&path_buf))
}
"azw" => {
let mut handler = AzwHandler::new();
handler
.set_metadata(metadata)
.and_then(|_| handler.set_content(content))
.and_then(|_| handler.write_to_file(&path_buf))
}
"fb2" => {
let mut handler = Fb2Handler::new();
handler
.set_metadata(metadata)
.and_then(|_| handler.set_content(content))
.and_then(|_| handler.write_to_file(&path_buf))
}
_ => {
return CallToolResult::error(vec![Content::text(format!(
"Unsupported format: {format}"
))]);
}
};
match res {
Ok(()) => CallToolResult::success(vec![Content::text(format!(
"Successfully wrote ebook to {path}"
))]),
Err(e) => CallToolResult::error(vec![Content::text(format!("Failed to write: {e}"))]),
}
}
pub(super) async fn extract_images(args: ExtractImagesArgs) -> CallToolResult {
let path = args.path;
let path_buf = PathBuf::from(&path);
let format = match crate::utils::detect_format(&path_buf) {
Ok(f) => f,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to detect format: {e}"
))]);
}
};
let images = match format.as_str() {
"epub" => {
let mut handler = EpubHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read EPUB: {e}"
))]);
}
match handler.extract_images() {
Ok(i) => i,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to extract images: {e}"
))]);
}
}
}
"cbz" => {
let mut handler = CbzHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read CBZ: {e}"
))]);
}
match handler.extract_images() {
Ok(i) => i,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to extract images: {e}"
))]);
}
}
}
_ => {
return CallToolResult::error(vec![Content::text(format!(
"Format {format} does not support image extraction"
))]);
}
};
let mut content = vec![];
for image in images {
let base64_data =
base64::engine::general_purpose::STANDARD.encode(&image.data);
content.push(Content::image(base64_data, image.mime_type));
}
CallToolResult::success(content)
}
pub(super) async fn validate_ebook(args: ValidateEbookArgs) -> CallToolResult {
let path = args.path;
let path_buf = PathBuf::from(&path);
let format = match crate::utils::detect_format(&path_buf) {
Ok(f) => f,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to detect format: {e}"
))]);
}
};
let is_valid = match format.as_str() {
"txt" => {
let mut handler = TxtHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read TXT: {e}"
))]);
}
match handler.validate() {
Ok(v) => v,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to validate: {e}"
))]);
}
}
}
"epub" => {
let mut handler = EpubHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read EPUB: {e}"
))]);
}
match handler.validate() {
Ok(v) => v,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to validate: {e}"
))]);
}
}
}
"pdf" => {
let mut handler = PdfHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read PDF: {e}"
))]);
}
match handler.validate() {
Ok(v) => v,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to validate: {e}"
))]);
}
}
}
"mobi" => {
let mut handler = MobiHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read MOBI: {e}"
))]);
}
match handler.validate() {
Ok(v) => v,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to validate: {e}"
))]);
}
}
}
"azw" => {
let mut handler = AzwHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read AZW: {e}"
))]);
}
match handler.validate() {
Ok(v) => v,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to validate: {e}"
))]);
}
}
}
"fb2" => {
let mut handler = Fb2Handler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read FB2: {e}"
))]);
}
match handler.validate() {
Ok(v) => v,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to validate: {e}"
))]);
}
}
}
"cbz" => {
let mut handler = CbzHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read CBZ: {e}"
))]);
}
match handler.validate() {
Ok(v) => v,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to validate: {e}"
))]);
}
}
}
_ => {
return CallToolResult::error(vec![Content::text(format!(
"Validation not supported for format: {format}"
))]);
}
};
let text = if is_valid {
format!("✓ File {path} is valid")
} else {
format!("✗ File {path} has validation issues")
};
CallToolResult::success(vec![Content::text(text)])
}
pub(super) async fn get_ebook_info(args: GetEbookInfoArgs) -> CallToolResult {
let path = args.path;
let path_buf = PathBuf::from(&path);
let format = match crate::utils::detect_format(&path_buf) {
Ok(f) => f,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to detect format: {e}"
))]);
}
};
let info = match format.as_str() {
"txt" => {
let mut handler = TxtHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read TXT: {e}"
))]);
}
let metadata = match handler.get_metadata() {
Ok(m) => m,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get metadata: {e}"
))]);
}
};
let content = match handler.get_content() {
Ok(c) => c,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get content: {e}"
))]);
}
};
format!(
"File: {path}\nFormat: {format}\nMetadata:\n{}\n\nSize: {} characters",
serde_json::to_string_pretty(&metadata).unwrap(),
content.len()
)
}
"epub" => {
let mut handler = EpubHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read EPUB: {e}"
))]);
}
let metadata = match handler.get_metadata() {
Ok(m) => m,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get metadata: {e}"
))]);
}
};
format!(
"File: {path}\nFormat: {format}\nMetadata:\n{}",
serde_json::to_string_pretty(&metadata).unwrap()
)
}
"pdf" => {
let mut handler = PdfHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read PDF: {e}"
))]);
}
let metadata = match handler.get_metadata() {
Ok(m) => m,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get metadata: {e}"
))]);
}
};
format!(
"File: {path}\nFormat: {format}\nMetadata:\n{}",
serde_json::to_string_pretty(&metadata).unwrap()
)
}
"mobi" => {
let mut handler = MobiHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read MOBI: {e}"
))]);
}
let metadata = match handler.get_metadata() {
Ok(m) => m,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get metadata: {e}"
))]);
}
};
format!(
"File: {path}\nFormat: {format}\nMetadata:\n{}",
serde_json::to_string_pretty(&metadata).unwrap()
)
}
"azw" => {
let mut handler = AzwHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read AZW: {e}"
))]);
}
let metadata = match handler.get_metadata() {
Ok(m) => m,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get metadata: {e}"
))]);
}
};
format!(
"File: {path}\nFormat: {format}\nMetadata:\n{}",
serde_json::to_string_pretty(&metadata).unwrap()
)
}
"fb2" => {
let mut handler = Fb2Handler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read FB2: {e}"
))]);
}
let metadata = match handler.get_metadata() {
Ok(m) => m,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get metadata: {e}"
))]);
}
};
format!(
"File: {path}\nFormat: {format}\nMetadata:\n{}",
serde_json::to_string_pretty(&metadata).unwrap()
)
}
"cbz" => {
let mut handler = CbzHandler::new();
if let Err(e) = handler.read_from_file(&path_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read CBZ: {e}"
))]);
}
let metadata = match handler.get_metadata() {
Ok(m) => m,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to get metadata: {e}"
))]);
}
};
format!(
"File: {path}\nFormat: {format}\nMetadata:\n{}",
serde_json::to_string_pretty(&metadata).unwrap()
)
}
_ => {
return CallToolResult::error(vec![Content::text(format!(
"Info not supported for format: {format}"
))]);
}
};
CallToolResult::success(vec![Content::text(info)])
}
pub(super) async fn convert_ebook(args: ConvertEbookArgs) -> CallToolResult {
let input_buf = PathBuf::from(&args.input_path);
let output_buf = PathBuf::from(&args.output_path);
match Converter::convert(&input_buf, &output_buf, &args.target_format) {
Ok(()) => CallToolResult::success(vec![Content::text(format!(
"Successfully converted {} to {} format",
args.input_path, args.target_format
))]),
Err(e) => CallToolResult::error(vec![Content::text(format!("Conversion failed: {e}"))]),
}
}
pub(super) async fn optimize_images(args: OptimizeImagesArgs) -> CallToolResult {
let input_path = args.input_path;
let output_path = args
.output_path
.as_deref()
.unwrap_or(input_path.as_str());
let max_width = args.max_width as u32;
let max_height = args.max_height as u32;
let quality = args.quality as u8;
let no_resize = args.no_resize;
let input_buf = PathBuf::from(&input_path);
let output_buf = PathBuf::from(output_path);
let format = match crate::utils::detect_format(&input_buf) {
Ok(f) => f,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to detect format: {e}"
))]);
}
};
let mut options = OptimizationOptions::default().with_quality(quality);
if no_resize {
options = options.no_resize();
} else {
options = options.with_max_dimensions(max_width, max_height);
}
let savings = match format.as_str() {
"epub" => {
let mut handler = EpubHandler::new();
if let Err(e) = handler.read_from_file(&input_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read EPUB: {e}"
))]);
}
let savings = match handler.optimize_images(options) {
Ok(s) => s,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to optimize images: {e}"
))]);
}
};
if let Err(e) = handler.write_to_file(&output_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to write EPUB: {e}"
))]);
}
savings
}
"cbz" => {
let mut handler = CbzHandler::new();
if let Err(e) = handler.read_from_file(&input_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to read CBZ: {e}"
))]);
}
let savings = match handler.optimize_images(options) {
Ok(s) => s,
Err(e) => {
return CallToolResult::error(vec![Content::text(format!(
"Failed to optimize images: {e}"
))]);
}
};
if let Err(e) = handler.write_to_file(&output_buf) {
return CallToolResult::error(vec![Content::text(format!(
"Failed to write CBZ: {e}"
))]);
}
savings
}
_ => {
return CallToolResult::error(vec![Content::text(format!(
"Image optimization only supports EPUB and CBZ formats, got: {format}"
))]);
}
};
let savings_mb = savings as f64 / 1024.0 / 1024.0;
let message = format!(
"Successfully optimized images in {input_path}\nSaved: {savings} bytes ({savings_mb:.2} MB)\nOutput: {output_path}"
);
CallToolResult::success(vec![Content::text(message)])
}