diff options
| author | A Farzat <a@farzat.xyz> | 2026-03-02 10:05:46 +0300 |
|---|---|---|
| committer | A Farzat <a@farzat.xyz> | 2026-03-02 11:27:12 +0300 |
| commit | 98532dc37ae9f72e8970d8d67e6bab9545596650 (patch) | |
| tree | 28b9f133106354c64101073180319105bc39fd2a | |
| parent | c6ffa62423659cf7d82802b9e0343f149c780de3 (diff) | |
| download | oreilly-epub-98532dc37ae9f72e8970d8d67e6bab9545596650.tar.gz oreilly-epub-98532dc37ae9f72e8970d8d67e6bab9545596650.zip | |
Add e-book metadata fetching
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | src/main.rs | 58 | ||||
| -rw-r--r-- | src/models.rs | 25 |
4 files changed, 75 insertions, 10 deletions
@@ -821,6 +821,7 @@ dependencies = [ "anyhow", "clap", "reqwest", + "serde", "serde_json", "tokio", ] @@ -7,5 +7,6 @@ edition = "2024" anyhow = "1.0.102" clap = { version = "4.5.60", features = ["derive"] } reqwest = { version = "0.13.2", features = ["cookies", "json"] } +serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" tokio = { version = "1.49.0", features = ["full"] } diff --git a/src/main.rs b/src/main.rs index 33c8d82..165d6be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,11 @@ mod http_client; +mod models; -use anyhow::Result; +use anyhow::{Context, Result}; use clap::Parser; use http_client::build_authenticated_client; +use models::{EpubResponse, SearchResponse}; +use reqwest::Client; /// Download and generate an EPUB from Safari Books Online. #[derive(Parser, Debug)] @@ -19,6 +22,34 @@ struct Args { preserve_log: bool, } +/// Fetches book metadata from the search endpoint. +async fn fetch_metadata(client: &Client, bookid: &str) -> Result<SearchResponse> { + let url = format!("https://learning.oreilly.com/api/v2/search/?query={bookid}&limit=1"); + let response = client + .get(&url) + .send() + .await? + .error_for_status()? + .json::<SearchResponse>() + .await + .context("Failed to deserialize Search API response.")?; + Ok(response) +} + +/// Fetches EPUB structural data (like the chapters URL) +async fn fetch_epub_data(client: &Client, bookid: &str) -> Result<EpubResponse> { + let url = format!("https://learning.oreilly.com/api/v2/epubs/urn:orm:book:{bookid}/"); + let response = client + .get(&url) + .send() + .await? + .error_for_status()? + .json::<EpubResponse>() + .await + .context("Failed to deserialize EPUB API response")?; + Ok(response) +} + #[tokio::main] async fn main() -> Result<()> { // Parse the command line arguments @@ -31,17 +62,24 @@ async fn main() -> Result<()> { println!("Loading cookies and initialising the HTTP client..."); let client = build_authenticated_client(&args.cookies)?; - // Quick test request to verify authentication. - let profile_url = "https://learning.oreilly.com/profile/"; - let response = client.get(profile_url).send().await?; - if response.status().is_success() { - println!("Successfully authenticated!"); + println!("Fetching book metadata..."); + // Fetch from the search API. + let search_data = fetch_metadata(&client, &args.bookid).await?; + if let Some(book) = search_data.results.first() { + println!("\n--- Book Found ---"); + println!("Title: {}", book.title); + println!("Authors: {}", book.authors.join(", ")); + println!("Publisher: {}", book.publishers.join(", ")); + println!("Cover URL: {}", book.cover_url); } else { - println!( - "Authentication might have failed. Status code: {}", - response.status() - ); + anyhow::bail!("Could not find book metadata for ID: {}", args.bookid); } + // Fetch from the EPUB API. + let epub_data = fetch_epub_data(&client, &args.bookid).await?; + println!("Publication date: {}", epub_data.publication_date); + println!("Chapters URL: {}", epub_data.chapters); + println!("Resources URL: {}", epub_data.files); + println!("------------------\n"); Ok(()) } diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..77afeb2 --- /dev/null +++ b/src/models.rs @@ -0,0 +1,25 @@ +use serde::Deserialize; + +// --- Models for the Search API --- + +#[derive(Debug, Deserialize)] +pub struct SearchResponse { + pub results: Vec<SearchResult>, +} + +#[derive(Debug, Deserialize)] +pub struct SearchResult { + pub title: String, + pub authors: Vec<String>, + pub publishers: Vec<String>, + pub cover_url: String, +} + +// --- Models for the EPUB API --- + +#[derive(Debug, Deserialize)] +pub struct EpubResponse { + pub publication_date: String, + pub chapters: String, // This is a URL to the chapters list + pub files: String, // This is a URL to the resource files +} |
