Skip to content

Commit

Permalink
Support authentication via cookies
Browse files Browse the repository at this point in the history
  • Loading branch information
hurlenko committed Nov 7, 2021
1 parent ee50401 commit 5977e7d
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 43 deletions.
34 changes: 20 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "orly"
version = "0.1.0"
version = "0.1.1"
edition = "2018"
authors = ["hurlenko"]
description = "Download O'Reilly books as EPUB"
Expand Down Expand Up @@ -31,7 +31,7 @@ askama = "0.10.5"
bytes = "1.0.1"
zip = "0.5.13"
lazy_static = "1.4.0"
clap = "3.0.0-beta.4"
clap = "3.0.0-beta.5"
sanitize-filename = "0.3.0"
log = "0.4.14"
fern = { version="0.6.0", features=["colored"] }
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,12 @@ After installation, the `orly` command will be available. Check the [command lin

- Find the book you want to download and copy its id (the digits at the end of the url).

- Use your credentials to download the book:
- Use your credentials or a cookie string to download the book:

```bash
orly --creds "[email protected]" "password" 1234567890
orly 1234567890 --creds "[email protected]" "password"
# or
orly 1234567890 --cookie 'BrowserCookie=....'
```

## Command line interface
Expand All @@ -101,6 +103,7 @@ FLAGS:
OPTIONS:
-c, --creds <EMAIL> <PASSWORD> Sign in credentials
--cookie <COOKIE_STRING> Cookie string
-o, --output <OUTPUT DIR> Directory to save the final epub to [default: .]
-t, --threads <THREADS> Sets the maximum number of concurrent http requests [default: 20]
```
56 changes: 40 additions & 16 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ use anyhow::Context;
use log::{error, info, trace};
use reqwest::{
header::{
HeaderMap, HeaderValue, ACCEPT, ACCEPT_ENCODING, UPGRADE_INSECURE_REQUESTS, USER_AGENT,
HeaderMap, HeaderValue, ACCEPT, ACCEPT_ENCODING, COOKIE, UPGRADE_INSECURE_REQUESTS,
USER_AGENT,
},
Client, Url,
Client, ClientBuilder, Url,
};

use crate::{
Expand Down Expand Up @@ -49,17 +50,8 @@ impl<S: AuthState> OreillyClient<S> {

impl Default for OreillyClient<Unauthenticated> {
fn default() -> Self {
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("application/json,text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"));
headers.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate"));
headers.insert(UPGRADE_INSECURE_REQUESTS, HeaderValue::from_static("1"));
headers.insert(USER_AGENT, HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"));
Self {
client: reqwest::Client::builder()
.default_headers(headers)
.cookie_store(true)
.build()
.expect("to build the client"),
client: Self::default_client().build().expect("to build the client"),
base_url: "https://learning.oreilly.com"
.parse()
.expect("correct base url"),
Expand All @@ -77,10 +69,20 @@ impl OreillyClient<Unauthenticated> {
}
}

async fn check_subscription(&self) -> Result<()> {
fn default_client() -> ClientBuilder {
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("application/json,text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"));
headers.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate"));
headers.insert(UPGRADE_INSECURE_REQUESTS, HeaderValue::from_static("1"));
headers.insert(USER_AGENT, HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"));
reqwest::Client::builder()
.default_headers(headers)
.cookie_store(true)
}

async fn check_subscription(&self, client: &Client) -> Result<()> {
info!("Validating subscription");
let response = self
.client
let response = client
.get(self.make_url("api/v1/payments/next_billing_date/")?)
.send()
.await?;
Expand Down Expand Up @@ -139,7 +141,7 @@ impl OreillyClient<Unauthenticated> {
));
}

self.check_subscription().await?;
self.check_subscription(&self.client).await?;

Ok(OreillyClient {
client: self.client,
Expand All @@ -148,6 +150,28 @@ impl OreillyClient<Unauthenticated> {
marker: std::marker::PhantomData,
})
}

pub async fn cookie_auth(self, cookie: &str) -> Result<OreillyClient<Authenticated>> {
info!("Logging into Safari Books Online usig cookies...");

let mut request_headers = HeaderMap::new();
request_headers.insert(
COOKIE,
HeaderValue::from_str(cookie).context("Invalid cookie")?,
);

let client = Self::default_client()
.default_headers(request_headers)
.build()?;
self.check_subscription(&client).await?;

Ok(OreillyClient {
client,
base_url: self.base_url,
concurrent_requests: self.concurrent_requests,
marker: std::marker::PhantomData,
})
}
}

impl OreillyClient<Authenticated> {
Expand Down
27 changes: 18 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use clap::{Clap, ValueHint};
use clap::{Parser, ValueHint};
use fern::colors::{Color, ColoredLevelConfig};
use log::{error, info};
use orly::{client::OreillyClient, epub::builder::EpubBuilder, error::Result, models::Book};
Expand All @@ -15,18 +15,26 @@ fn path_exists(v: &str) -> std::result::Result<(), String> {
Err(format!("The specifiied path does not exist: {}", v))
}

#[derive(Clap, Debug)]
#[derive(Parser, Debug)]
#[clap(author, about, version)]
struct CliArgs {
#[clap(
short,
long,
value_name = "EMAIL PASSWORD",
about = "Sign in credentials",
required = true,
required_unless_present = "cookie",
conflicts_with = "cookie",
number_of_values = 2
)]
creds: Vec<String>,
creds: Option<Vec<String>>,
#[clap(
long,
value_name = "COOKIE_STRING",
about = "Cookie string",
required_unless_present = "creds"
)]
cookie: Option<String>,
#[clap(
short,
long,
Expand Down Expand Up @@ -80,13 +88,14 @@ fn generate_filename(book: &Book) -> String {
}

async fn run(cli_args: &CliArgs) -> Result<()> {
let email = &cli_args.creds[0];
let password = &cli_args.creds[1];
let book_id = &cli_args.book_id;

let client = OreillyClient::new(cli_args.threads)
.cred_auth(email, password)
.await?;
let client = OreillyClient::new(cli_args.threads);
let client = if let Some(creds) = &cli_args.creds {
client.cred_auth(&creds[0], &creds[1]).await?
} else {
client.cookie_auth(cli_args.cookie.as_ref().unwrap()).await?
};

info!("Getting book info");
let book = client.fetch_book_details(book_id).await?;
Expand Down

0 comments on commit 5977e7d

Please sign in to comment.