From 9f56c58dd93c738d9afc33ffcf9572b929672c0c Mon Sep 17 00:00:00 2001 From: Kenneth Gitere Date: Sat, 16 May 2020 10:09:44 +0300 Subject: [PATCH 01/27] Add simple CLI wrapper --- Cargo.lock | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/cli.rs | 13 +++++ src/main.rs | 44 ++++++++--------- 4 files changed, 170 insertions(+), 21 deletions(-) create mode 100644 src/cli.rs diff --git a/Cargo.lock b/Cargo.lock index f48fe72..fafdfb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.8", +] + [[package]] name = "async-std" version = "1.5.0" @@ -50,6 +59,17 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.8", +] + [[package]] name = "autocfg" version = "0.1.7" @@ -155,6 +175,21 @@ dependencies = [ "time", ] +[[package]] +name = "clap" +version = "2.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -523,6 +558,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.12" @@ -972,6 +1016,7 @@ dependencies = [ "epub-builder", "kuchiki", "md5", + "structopt", "surf", "url", ] @@ -1110,6 +1155,32 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "proc-macro-error" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98e9e4b82e0ef281812565ea4751049f1bdcdfccda7d3f459f2e138a40c08678" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "syn-mid", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.15" @@ -1577,6 +1648,36 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863246aaf5ddd0d6928dfeb1a9ca65f505599e4e1b399935ef7e75107516b4ef" +dependencies = [ + "clap", + "lazy_static 1.4.0", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d239ca4b13aee7a2142e6795cbd69e457665ff8037aed33b3effdc430d2f927a" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "surf" version = "1.0.3" @@ -1610,6 +1711,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "syn-mid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tempdir" version = "0.3.7" @@ -1631,6 +1743,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thin-slice" version = "0.1.1" @@ -1694,6 +1815,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + [[package]] name = "unicode-width" version = "0.1.7" @@ -1747,6 +1874,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.1" diff --git a/Cargo.toml b/Cargo.toml index 867a34a..801a3a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ epub-builder = "0.4.5" kuchiki = "0.8.0" md5 = "0.7.0" surf = "1.0.3" +structopt = { version = "0.3" } url = "2.1.1" \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..ba0273d --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,13 @@ +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt(name = "paperoni")] +/// Paperoni is an article downloader. +/// +/// It takes a url and downloads the article content from it and +/// saves it to an epub. +pub struct Opts { + // #[structopt(conflicts_with("links"))] + /// Url of a web article + pub url: Option, +} diff --git a/src/main.rs b/src/main.rs index d790f9b..6f15e9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,22 +2,35 @@ use std::fs::File; use async_std::{fs::create_dir, fs::remove_dir_all, task}; use epub_builder::{EpubBuilder, EpubContent, ZipLibrary}; +use structopt::StructOpt; use url::Url; +mod cli; mod extractor; use extractor::Extractor; fn main() { + let opt = cli::Opts::from_args(); + if let Some(url) = opt.url { + println!("Downloading single article"); + download(url) + } +} + +async fn fetch_url(url: &str) -> String { + let client = surf::Client::new(); + println!("Fetching..."); + // TODO: Add middleware for following redirects + client + .get(url) + .recv_string() + .await + .expect("Unable to fetch URL") +} + +fn download(url: String) { task::block_on(async { - let urls = vec![ - "https://saveandrun.com/posts/2020-01-24-generating-mazes-with-haskell-part-1.html", - "https://saveandrun.com/posts/2020-04-05-querying-pacman-with-datalog.html", - "https://blog.hipstermojo.xyz/posts/redis-orm-preface/", - "https://vuejsdevelopers.com/2020/03/31/vue-js-form-composition-api/?utm_campaign=xl5&utm_medium=article&utm_source=vuejsnews#adding-validators", - "https://medium.com/typeforms-engineering-blog/the-beginners-guide-to-oauth-dancing-4b8f3666de10", - "https://dev.to/steelwolf180/full-stack-development-in-django-3768" - ]; - let html = fetch_url(urls[4]).await; + let html = fetch_url(&url).await; let mut extractor = Extractor::from_html(&html); println!("Extracting"); extractor.extract_content(); @@ -25,7 +38,7 @@ fn main() { .await .expect("Unable to create res/ output folder"); extractor - .download_images(&Url::parse(urls[5]).unwrap()) + .download_images(&Url::parse(&url).unwrap()) .await .expect("Unable to download images"); let mut out_file = File::create("out.epub").unwrap(); @@ -51,14 +64,3 @@ fn main() { remove_dir_all("res/").await.unwrap(); }) } - -async fn fetch_url(url: &str) -> String { - let client = surf::Client::new(); - println!("Fetching..."); - // TODO: Add middleware for following redirects - client - .get(url) - .recv_string() - .await - .expect("Unable to fetch URL") -} From 6dab011cac3573f2ecd1e12ffaddc85a9af2dd6a Mon Sep 17 00:00:00 2001 From: Kenneth Gitere Date: Sat, 16 May 2020 10:22:49 +0300 Subject: [PATCH 02/27] Fixed img resolving bug --- out.epub | Bin 0 -> 42500 bytes src/extractor.rs | 46 ++++++++++++++++++++++------------------------ 2 files changed, 22 insertions(+), 24 deletions(-) create mode 100644 out.epub diff --git a/out.epub b/out.epub new file mode 100644 index 0000000000000000000000000000000000000000..df0d53d7a1986c0cd5b2fd35fae8d06eb7a20178 GIT binary patch literal 42500 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 = img_response.body_bytes().await.unwrap(); let img_mime = img_response .header("Content-Type") @@ -114,7 +114,7 @@ impl Extractor { .and_then(map_mime_type_to_ext) .unwrap(); - let img_path = format!("res/{}{}", hash_url(&img_url), &img_ext); + let img_path = format!("res/{}{}", hash_url(&abs_url), &img_ext); let mut img_file = File::create(&img_path) .await .expect("Unable to create file"); @@ -174,10 +174,11 @@ fn map_mime_type_to_ext(mime_type: &str) -> Option { .map(|format| String::from(".") + format) } -fn get_absolute_url(url: &mut String, request_url: &Url) { +fn get_absolute_url(url: &str, request_url: &Url) -> String { if Url::parse(url).is_ok() { + url.to_owned() } else if url.starts_with("/") { - *url = Url::parse(&format!( + Url::parse(&format!( "{}://{}", request_url.scheme(), request_url.host_str().unwrap() @@ -185,9 +186,9 @@ fn get_absolute_url(url: &mut String, request_url: &Url) { .unwrap() .join(url) .unwrap() - .into_string(); + .into_string() } else { - *url = request_url.join(url).unwrap().into_string(); + request_url.join(url).unwrap().into_string() } } @@ -356,24 +357,21 @@ mod test { #[test] fn test_get_absolute_url() { - let mut absolute_url = "https://example.image.com/images/1.jpg".to_owned(); - let mut relative_url = "../../images/2.jpg".to_owned(); - let mut relative_from_host_url = "/images/3.jpg".to_owned(); + let absolute_url = "https://example.image.com/images/1.jpg"; + let relative_url = "../../images/2.jpg"; + let relative_from_host_url = "/images/3.jpg"; let host_url = Url::parse("https://example.image.com/blog/how-to-test-resolvers/").unwrap(); - get_absolute_url(&mut absolute_url, &host_url); - assert_eq!("https://example.image.com/images/1.jpg", absolute_url); - get_absolute_url(&mut relative_url, &host_url); - assert_eq!("https://example.image.com/images/2.jpg", relative_url); - relative_url = "2-1.jpg".to_owned(); - get_absolute_url(&mut relative_url, &host_url); + let abs_url = get_absolute_url(&absolute_url, &host_url); + assert_eq!("https://example.image.com/images/1.jpg", abs_url); + let abs_url = get_absolute_url(&relative_url, &host_url); + assert_eq!("https://example.image.com/images/2.jpg", abs_url); + let relative_url = "2-1.jpg"; + let abs_url = get_absolute_url(&relative_url, &host_url); assert_eq!( "https://example.image.com/blog/how-to-test-resolvers/2-1.jpg", - relative_url - ); - get_absolute_url(&mut relative_from_host_url, &host_url); - assert_eq!( - "https://example.image.com/images/3.jpg", - relative_from_host_url + abs_url ); + let abs_url = get_absolute_url(&relative_from_host_url, &host_url); + assert_eq!("https://example.image.com/images/3.jpg", abs_url); } } From 5e7cf7ddfe2b12f7fe8575fa3ec761d0f24a373a Mon Sep 17 00:00:00 2001 From: Kenneth Gitere Date: Sat, 16 May 2020 10:22:49 +0300 Subject: [PATCH 03/27] Fixed img resolving bug --- src/extractor.rs | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index 2355939..b393f5b 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -101,10 +101,10 @@ impl Extractor { self.extract_img_urls(); println!("Downloading images to res/"); for img_url in &self.img_urls { - let mut img_url = img_url.0.clone(); - get_absolute_url(&mut img_url, article_origin); - async_download_tasks.push(task::spawn(async { - let mut img_response = surf::get(&img_url).await.expect("Unable to retrieve file"); + let img_url = img_url.0.clone(); + let abs_url = get_absolute_url(&img_url, article_origin); + async_download_tasks.push(task::spawn(async move { + let mut img_response = surf::get(&abs_url).await.expect("Unable to retrieve file"); let img_content: Vec = img_response.body_bytes().await.unwrap(); let img_mime = img_response .header("Content-Type") @@ -114,7 +114,7 @@ impl Extractor { .and_then(map_mime_type_to_ext) .unwrap(); - let img_path = format!("res/{}{}", hash_url(&img_url), &img_ext); + let img_path = format!("res/{}{}", hash_url(&abs_url), &img_ext); let mut img_file = File::create(&img_path) .await .expect("Unable to create file"); @@ -174,10 +174,11 @@ fn map_mime_type_to_ext(mime_type: &str) -> Option { .map(|format| String::from(".") + format) } -fn get_absolute_url(url: &mut String, request_url: &Url) { +fn get_absolute_url(url: &str, request_url: &Url) -> String { if Url::parse(url).is_ok() { + url.to_owned() } else if url.starts_with("/") { - *url = Url::parse(&format!( + Url::parse(&format!( "{}://{}", request_url.scheme(), request_url.host_str().unwrap() @@ -185,9 +186,9 @@ fn get_absolute_url(url: &mut String, request_url: &Url) { .unwrap() .join(url) .unwrap() - .into_string(); + .into_string() } else { - *url = request_url.join(url).unwrap().into_string(); + request_url.join(url).unwrap().into_string() } } @@ -356,24 +357,21 @@ mod test { #[test] fn test_get_absolute_url() { - let mut absolute_url = "https://example.image.com/images/1.jpg".to_owned(); - let mut relative_url = "../../images/2.jpg".to_owned(); - let mut relative_from_host_url = "/images/3.jpg".to_owned(); + let absolute_url = "https://example.image.com/images/1.jpg"; + let relative_url = "../../images/2.jpg"; + let relative_from_host_url = "/images/3.jpg"; let host_url = Url::parse("https://example.image.com/blog/how-to-test-resolvers/").unwrap(); - get_absolute_url(&mut absolute_url, &host_url); - assert_eq!("https://example.image.com/images/1.jpg", absolute_url); - get_absolute_url(&mut relative_url, &host_url); - assert_eq!("https://example.image.com/images/2.jpg", relative_url); - relative_url = "2-1.jpg".to_owned(); - get_absolute_url(&mut relative_url, &host_url); + let abs_url = get_absolute_url(&absolute_url, &host_url); + assert_eq!("https://example.image.com/images/1.jpg", abs_url); + let abs_url = get_absolute_url(&relative_url, &host_url); + assert_eq!("https://example.image.com/images/2.jpg", abs_url); + let relative_url = "2-1.jpg"; + let abs_url = get_absolute_url(&relative_url, &host_url); assert_eq!( "https://example.image.com/blog/how-to-test-resolvers/2-1.jpg", - relative_url - ); - get_absolute_url(&mut relative_from_host_url, &host_url); - assert_eq!( - "https://example.image.com/images/3.jpg", - relative_from_host_url + abs_url ); + let abs_url = get_absolute_url(&relative_from_host_url, &host_url); + assert_eq!("https://example.image.com/images/3.jpg", abs_url); } } From e1debf5630df594c0e587261352591d75d3cdb2b Mon Sep 17 00:00:00 2001 From: Kenneth Gitere Date: Mon, 31 Aug 2020 19:30:09 +0300 Subject: [PATCH 04/27] Add moz_readability initial code and accompanying unit tests This currently contains the preprocessing code of the Readability. It is a port of Readability.js by Mozilla. --- Cargo.lock | 5 +- Cargo.toml | 3 +- src/main.rs | 1 + src/moz_readability/mod.rs | 653 +++++++++++++++++++++++++++++++++++++ test_html/simple.html | 25 ++ 5 files changed, 684 insertions(+), 3 deletions(-) create mode 100644 src/moz_readability/mod.rs create mode 100644 test_html/simple.html diff --git a/Cargo.lock b/Cargo.lock index fafdfb7..f3ad4b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -704,9 +704,9 @@ dependencies = [ [[package]] name = "kuchiki" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1beeffc5ae5ab0def2cb85e26063a8e6b4f579b0adec3805bf87472086948956" +checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" dependencies = [ "cssparser", "html5ever 0.25.1", @@ -1014,6 +1014,7 @@ version = "0.1.0" dependencies = [ "async-std", "epub-builder", + "html5ever 0.25.1", "kuchiki", "md5", "structopt", diff --git a/Cargo.toml b/Cargo.toml index 801a3a8..64afcb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,8 @@ license = "MIT" [dependencies] async-std = "1.5.0" epub-builder = "0.4.5" -kuchiki = "0.8.0" +html5ever = "0.25.1" +kuchiki = "0.8.1" md5 = "0.7.0" surf = "1.0.3" structopt = { version = "0.3" } diff --git a/src/main.rs b/src/main.rs index 6f15e9e..8284875 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use url::Url; mod cli; mod extractor; +mod moz_readability; use extractor::Extractor; fn main() { diff --git a/src/moz_readability/mod.rs b/src/moz_readability/mod.rs new file mode 100644 index 0000000..170d7e8 --- /dev/null +++ b/src/moz_readability/mod.rs @@ -0,0 +1,653 @@ +use std::collections::BTreeMap; + +use crate::extractor::MetaAttr; + +use html5ever::{LocalName, Namespace, QualName}; +use kuchiki::{ + iter::{Descendants, Elements, Select}, + traits::*, + NodeData, NodeRef, +}; + +const HTML_NS: &'static str = "http://www.w3.org/1999/xhtml"; +const PHRASING_ELEMS: [&str; 39] = [ + "abbr", "audio", "b", "bdo", "br", "button", "cite", "code", "data", "datalist", "dfn", "em", + "embed", "i", "img", "input", "kbd", "label", "mark", "math", "meter", "noscript", "object", + "output", "progress", "q", "ruby", "samp", "script", "select", "small", "span", "strong", + "sub", "sup", "textarea", "time", "var", "wbr", +]; + +pub struct Readability { + root_node: NodeRef, +} + +impl Readability { + pub fn new(html_str: &str) -> Self { + Self { + root_node: kuchiki::parse_html().one(html_str), + } + } + pub fn parse(&mut self) { + self.unwrap_no_script_tags(); + } + /// Recursively check if node is image, or if node contains exactly only one image + /// whether as a direct child or as its descendants. + fn is_single_image(node_ref: &NodeRef) -> bool { + if let Some(element) = node_ref.as_element() { + if &element.name.local == "img" { + return true; + } + } + + if node_ref.children().filter(Self::has_content).count() != 1 + || !node_ref.text_contents().trim().is_empty() + { + return false; + } + + return Readability::is_single_image( + &node_ref + .children() + .filter(Self::has_content) + .next() + .expect("Unable to get first child which should exist"), + ); + } + + fn has_content(node_ref: &NodeRef) -> bool { + match node_ref.data() { + NodeData::Text(text) => !text.borrow().trim().is_empty(), + _ => true, + } + } + + /// Find all