From 57bc69a7f9af497526695e5a0bfbc60939f667e9 Mon Sep 17 00:00:00 2001 From: A Farzat Date: Thu, 12 Feb 2026 14:23:43 +0300 Subject: Add the ability to fetch book info --- Cargo.lock | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- src/main.rs | 11 +++++-- src/orly.rs | 29 +++++++++++++++++++ 4 files changed, 135 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef277f6..1b1a448 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -67,6 +73,18 @@ version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +[[package]] +name = "async-compression" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -223,6 +241,23 @@ dependencies = [ "memchr", ] +[[package]] +name = "compression-codecs" +version = "0.4.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + [[package]] name = "core-foundation" version = "0.10.1" @@ -239,6 +274,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -262,6 +306,16 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -292,6 +346,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + [[package]] name = "futures-task" version = "0.3.31" @@ -660,6 +720,16 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.1" @@ -883,6 +953,8 @@ dependencies = [ "rustls", "rustls-pki-types", "rustls-platform-verifier", + "serde", + "serde_json", "sync_wrapper", "tokio", "tokio-rustls", @@ -1110,6 +1182,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "slab" version = "0.4.12" @@ -1291,6 +1369,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tower" version = "0.5.3" @@ -1312,13 +1403,18 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ + "async-compression", "bitflags", "bytes", + "futures-core", "futures-util", "http", "http-body", + "http-body-util", "iri-string", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", diff --git a/Cargo.toml b/Cargo.toml index 42e52da..70b1e11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" anyhow = "1.0" clap = { version = "4.5", features = ["derive"] } colored = "3.1" -reqwest = { version = "0.13", default-features = false, features = ["rustls"] } +reqwest = { version = "0.13", default-features = false, features = ["gzip", "json", "rustls"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tokio = { version = "1.49", features = ["rt-multi-thread", "macros"] } diff --git a/src/main.rs b/src/main.rs index f79f191..d393a55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,7 @@ use cli::Args; use cookies::CookieStore; use display::Display; use http_client::HttpClient; -use orly::check_login; -use reqwest::Client; +use orly::{check_login, fetch_book_info}; #[tokio::main] async fn main() { @@ -60,6 +59,14 @@ async fn main() { Err(e) => ui.error_and_exit(&format!("Login check failed: {e}")), }; + // Retrieve book info. + ui.info("Retrieving book info..."); + let bookinfo = match fetch_book_info(&client, &args.bookid).await { + Ok(info) => info, + Err(e) => ui.error_and_exit(&format!("Failed to fetch book info: {}", e)), + }; + ui.info(&format!("{:#?}", bookinfo)); + let output_dir = config::books_root().join(format!("(pending) ({})", args.bookid)); ui.set_output_dir(output_dir); diff --git a/src/orly.rs b/src/orly.rs index cd8b645..0e34e5c 100644 --- a/src/orly.rs +++ b/src/orly.rs @@ -1,8 +1,16 @@ use crate::http_client::HttpClient; use anyhow::{bail, Result}; +use serde::Deserialize; pub const PROFILE_URL: &str = "https://learning.oreilly.com/profile/"; +/// Minimal subset of the book that we care about. +#[derive(Debug, Deserialize)] +pub struct BookInfo { + pub title: String, + pub web_url: String, +} + /// Check whether cookies keep us logged in by fetching the profile page. /// Returns: /// - Ok(true) => HTTP 200 (assume logged in) @@ -20,3 +28,24 @@ pub async fn check_login(client: &HttpClient) -> Result { bail!("Profile request returned unexpected status {}", status) } } + +/// Build the v1 API URL for the book. +pub fn book_api_url(bookid: &str) -> String { + format!("https://learning.oreilly.com/api/v1/book/{bookid}") +} + +/// Fetch book metadata from the website. +pub async fn fetch_book_info(client: &HttpClient, bookid: &str) -> Result { + let url = book_api_url(bookid); + let res = client.client().get(url).send().await?; + let status = res.status(); + + if status == 200 { + let info = res.json::().await?; + return Ok(info); + } + if status == 404 { + bail!("Book not found (HTTP 404). Please double-check the book ID provided") + } + bail!("Got status: {}", status) +} -- cgit v1.2.3-70-g09d2