Update dependencies
This commit is contained in:
parent
56898120d9
commit
f12edb405e
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "hentaihavenrs"
|
name = "hentaihavenrs"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
authors = ["blank X <theblankx@protonmail.com>"]
|
authors = ["blank X <theblankx@protonmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
@ -10,9 +10,9 @@ edition = "2018"
|
||||||
lto = true
|
lto = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.1", features = ["rt"] }
|
tokio = { version = "1.18", features = ["rt"] }
|
||||||
reqwest = { version = "0.11", features = ["multipart", "rustls-tls"] }
|
reqwest = { version = "0.11", features = ["multipart", "rustls-tls"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
quick-xml = "0.20"
|
quick-xml = "0.22"
|
||||||
clap = { version = "2.33", default-features = false }
|
clap = { version = "3.1", features = ["std"], default-features = false }
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
|
|
||||||
|
use clap::ArgMatches;
|
||||||
|
use std::fs::{create_dir, rename};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::fs::{rename, create_dir};
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::{exit, Command};
|
use std::process::{exit, Command};
|
||||||
use clap::ArgMatches;
|
|
||||||
|
|
||||||
const MAX_DOWNLOAD_ATTEMPTS: i32 = 5;
|
const MAX_DOWNLOAD_ATTEMPTS: i32 = 5;
|
||||||
|
|
||||||
pub async fn download(arg_m: &ArgMatches<'_>) {
|
pub async fn download(arg_m: &ArgMatches) {
|
||||||
let print_only = arg_m.is_present("print");
|
let print_only = arg_m.is_present("print");
|
||||||
let id = arg_m.value_of("id").unwrap();
|
let id = arg_m.value_of("id").unwrap();
|
||||||
let client = utils::create_client();
|
let client = utils::create_client();
|
||||||
|
@ -26,17 +26,21 @@ pub async fn download(arg_m: &ArgMatches<'_>) {
|
||||||
if filename.exists() {
|
if filename.exists() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let download_url = match utils::get_url(tcloned_client.clone(), &episode_url).await {
|
let download_url =
|
||||||
Ok(Some(i)) => i,
|
match utils::get_url(tcloned_client.clone(), &episode_url).await {
|
||||||
Ok(None) => {
|
Ok(Some(i)) => i,
|
||||||
eprintln!("Failed to get {}: get_url returned None", filename.display());
|
Ok(None) => {
|
||||||
continue;
|
eprintln!(
|
||||||
},
|
"Failed to get {}: get_url returned None",
|
||||||
Err(err) => {
|
filename.display()
|
||||||
eprintln!("Failed to get {}: {}", filename.display(), err);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
Err(err) => {
|
||||||
|
eprintln!("Failed to get {}: {}", filename.display(), err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
if print_only {
|
if print_only {
|
||||||
println!("{}", download_url);
|
println!("{}", download_url);
|
||||||
continue;
|
continue;
|
||||||
|
@ -56,12 +60,17 @@ pub async fn download(arg_m: &ArgMatches<'_>) {
|
||||||
for i in 0..MAX_DOWNLOAD_ATTEMPTS {
|
for i in 0..MAX_DOWNLOAD_ATTEMPTS {
|
||||||
eprintln!("Downloading {} (attempt {})", filename.display(), i);
|
eprintln!("Downloading {} (attempt {})", filename.display(), i);
|
||||||
let mut command = Command::new("ffmpeg");
|
let mut command = Command::new("ffmpeg");
|
||||||
let command = command.args(&["-v", "warning", "-stats", "-nostdin", "-y", "-i"]);
|
let command =
|
||||||
|
command.args(&["-v", "warning", "-stats", "-nostdin", "-y", "-i"]);
|
||||||
let mut command = command.arg(&download_url.video);
|
let mut command = command.arg(&download_url.video);
|
||||||
if let Some(ref captions) = download_url.captions {
|
if let Some(ref captions) = download_url.captions {
|
||||||
command = command.args(&["-i", &captions]);
|
command = command.args(&["-i", &captions]);
|
||||||
}
|
}
|
||||||
match command.args(&["-c", "copy", "-f", "matroska"]).arg(&tmp_filename).spawn() {
|
match command
|
||||||
|
.args(&["-c", "copy", "-f", "matroska"])
|
||||||
|
.arg(&tmp_filename)
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
Ok(mut child) => {
|
Ok(mut child) => {
|
||||||
match child.wait() {
|
match child.wait() {
|
||||||
Ok(exit_status) => {
|
Ok(exit_status) => {
|
||||||
|
@ -69,16 +78,29 @@ pub async fn download(arg_m: &ArgMatches<'_>) {
|
||||||
fail_dl = false;
|
fail_dl = false;
|
||||||
match rename(&tmp_filename, &filename) {
|
match rename(&tmp_filename, &filename) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => eprintln!("Failed to rename {} to {} due to {}", tmp_filename.display(), filename.display(), err)
|
Err(err) => eprintln!(
|
||||||
|
"Failed to rename {} to {} due to {}",
|
||||||
|
tmp_filename.display(),
|
||||||
|
filename.display(),
|
||||||
|
err
|
||||||
|
),
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
eprintln!("ffmpeg exited with {:?}", exit_status.code());
|
eprintln!(
|
||||||
},
|
"ffmpeg exited with {:?}",
|
||||||
Err(err) => eprintln!("Failed to wait on ffmpeg process due to {}", err)
|
exit_status.code()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) => eprintln!(
|
||||||
|
"Failed to wait on ffmpeg process due to {}",
|
||||||
|
err
|
||||||
|
),
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
Err(err) => eprintln!("Failed to spawn ffmpeg process due to {}", err)
|
Err(err) => {
|
||||||
|
eprintln!("Failed to spawn ffmpeg process due to {}", err)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if fail_dl {
|
if fail_dl {
|
||||||
|
@ -86,13 +108,13 @@ pub async fn download(arg_m: &ArgMatches<'_>) {
|
||||||
return_fail = true;
|
return_fail = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
eprintln!("Failed to get {}: does not exist", id);
|
eprintln!("Failed to get {}: does not exist", id);
|
||||||
return_fail = true;
|
return_fail = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Failed to get {}: {}", id, err);
|
eprintln!("Failed to get {}: {}", id, err);
|
||||||
return_fail = true;
|
return_fail = true;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
mod view;
|
|
||||||
mod search;
|
|
||||||
mod download;
|
mod download;
|
||||||
pub use view::view;
|
mod search;
|
||||||
pub use search::search;
|
mod view;
|
||||||
pub use download::download;
|
pub use download::download;
|
||||||
|
pub use search::search;
|
||||||
|
pub use view::view;
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
|
|
||||||
use std::process::exit;
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
pub async fn search(arg_m: &ArgMatches<'_>) {
|
pub async fn search(arg_m: &ArgMatches) {
|
||||||
let query = arg_m.values_of("query").unwrap_or_default().collect::<Vec<_>>().join(" ");
|
let query = arg_m
|
||||||
|
.values_of("query")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ");
|
||||||
let query = query.trim();
|
let query = query.trim();
|
||||||
let results = utils::search(utils::create_client(), query).await.unwrap();
|
let results = utils::search(utils::create_client(), query).await.unwrap();
|
||||||
if results.is_empty() {
|
if results.is_empty() {
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
|
|
||||||
use std::process::exit;
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
|
use std::process::exit;
|
||||||
extern crate tokio;
|
extern crate tokio;
|
||||||
|
|
||||||
pub async fn view(arg_m: &ArgMatches<'_>) {
|
pub async fn view(arg_m: &ArgMatches) {
|
||||||
let client = utils::create_client();
|
let client = utils::create_client();
|
||||||
let handles = arg_m.values_of("id").unwrap().map(|id| {
|
let handles = arg_m
|
||||||
let cloned_client = client.clone();
|
.values_of("id")
|
||||||
let id = id.to_string();
|
.unwrap()
|
||||||
let cid = id.clone();
|
.map(|id| {
|
||||||
(tokio::spawn(async move {
|
let cloned_client = client.clone();
|
||||||
utils::get_hentai(cloned_client, &cid).await
|
let id = id.to_string();
|
||||||
}), id)
|
let cid = id.clone();
|
||||||
}).collect::<Vec<_>>();
|
(
|
||||||
|
tokio::spawn(async move { utils::get_hentai(cloned_client, &cid).await }),
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
let mut fail = false;
|
let mut fail = false;
|
||||||
let mut one_done = false;
|
let mut one_done = false;
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
|
@ -38,7 +43,7 @@ pub async fn view(arg_m: &ArgMatches<'_>) {
|
||||||
println!("");
|
println!("");
|
||||||
}
|
}
|
||||||
println!("{}", &hentai);
|
println!("{}", &hentai);
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
if one_done {
|
if one_done {
|
||||||
eprintln!("");
|
eprintln!("");
|
||||||
|
@ -47,7 +52,7 @@ pub async fn view(arg_m: &ArgMatches<'_>) {
|
||||||
fail = true;
|
fail = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if one_done {
|
if one_done {
|
||||||
eprintln!("");
|
eprintln!("");
|
||||||
|
|
58
src/main.rs
58
src/main.rs
|
@ -1,46 +1,40 @@
|
||||||
mod commands;
|
mod commands;
|
||||||
mod structs;
|
mod structs;
|
||||||
mod utils;
|
mod utils;
|
||||||
use clap::{App, AppSettings, Arg, SubCommand};
|
use clap::{Arg, Command};
|
||||||
extern crate tokio;
|
extern crate tokio;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let matches = App::new("hentaihavenrs")
|
let matches = Command::new("hentaihavenrs")
|
||||||
.about("hentaihaven.tv downloader in rust")
|
.about("hentaihaven.tv downloader in rust")
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
.subcommand_required(true)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("search")
|
Command::new("search").arg(
|
||||||
.arg(
|
Arg::new("query")
|
||||||
Arg::with_name("query")
|
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.multiple(true)
|
.multiple_values(true)
|
||||||
.help("Search query")
|
.help("Search query"),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("view")
|
Command::new("view").visible_aliases(&["info", "show"]).arg(
|
||||||
.aliases(&["info", "show"])
|
Arg::new("id")
|
||||||
.arg(
|
|
||||||
Arg::with_name("id")
|
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.multiple(true)
|
.multiple_values(true)
|
||||||
.required(true)
|
.required(true),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("download")
|
Command::new("download")
|
||||||
.alias("dl")
|
.visible_alias("dl")
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("print")
|
Arg::new("print")
|
||||||
.long("print")
|
.long("print")
|
||||||
.short("p")
|
.short('p')
|
||||||
.help("Print the URL to download only")
|
.help("Print the URL to download only"),
|
||||||
).arg(
|
)
|
||||||
Arg::with_name("id")
|
.arg(Arg::new("id").takes_value(true).required(true)),
|
||||||
.takes_value(true)
|
|
||||||
.required(true)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
|
@ -49,9 +43,9 @@ fn main() {
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
("search", Some(sub_m)) => runtime.block_on(commands::search(sub_m)),
|
Some(("search", sub_m)) => runtime.block_on(commands::search(sub_m)),
|
||||||
("view", Some(sub_m)) => runtime.block_on(commands::view(sub_m)),
|
Some(("view", sub_m)) => runtime.block_on(commands::view(sub_m)),
|
||||||
("download", Some(sub_m)) => runtime.block_on(commands::download(sub_m)),
|
Some(("download", sub_m)) => runtime.block_on(commands::download(sub_m)),
|
||||||
_ => panic!("AppSettings::SubcommandRequiredElseHelp do your job please")
|
_ => unreachable!("subcommand_required do your job please"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
use std::fmt;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
extern crate reqwest;
|
use std::fmt;
|
||||||
extern crate quick_xml;
|
extern crate quick_xml;
|
||||||
|
extern crate reqwest;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct SearchResult {
|
pub struct SearchResult {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub title: RenderedTitle
|
pub title: RenderedTitle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct RenderedTitle {
|
pub struct RenderedTitle {
|
||||||
pub rendered: String
|
pub rendered: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for SearchResult {
|
impl fmt::Display for SearchResult {
|
||||||
|
@ -30,18 +30,20 @@ pub struct HentaiInfo {
|
||||||
pub genres: Vec<String>,
|
pub genres: Vec<String>,
|
||||||
pub censored: bool,
|
pub censored: bool,
|
||||||
pub episode_urls: Vec<String>,
|
pub episode_urls: Vec<String>,
|
||||||
pub summary: String
|
pub summary: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for HentaiInfo {
|
impl fmt::Display for HentaiInfo {
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(formatter, "ID: {}\nTitle: {}\nViews: {}\nCensored: {}\nGenres: {}\nEpisodes: {}\nSummary:\n{}",
|
write!(
|
||||||
|
formatter,
|
||||||
|
"ID: {}\nTitle: {}\nViews: {}\nCensored: {}\nGenres: {}\nEpisodes: {}\nSummary:\n{}",
|
||||||
&self.id,
|
&self.id,
|
||||||
&self.title,
|
&self.title,
|
||||||
self.views,
|
self.views,
|
||||||
match self.censored {
|
match self.censored {
|
||||||
true => "Yes",
|
true => "Yes",
|
||||||
false => "No"
|
false => "No",
|
||||||
},
|
},
|
||||||
&self.genres.join(", "),
|
&self.genres.join(", "),
|
||||||
self.episode_urls.len(),
|
self.episode_urls.len(),
|
||||||
|
@ -53,7 +55,7 @@ impl fmt::Display for HentaiInfo {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HentaiVideo {
|
pub struct HentaiVideo {
|
||||||
pub captions: Option<String>,
|
pub captions: Option<String>,
|
||||||
pub video: String
|
pub video: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for HentaiVideo {
|
impl fmt::Display for HentaiVideo {
|
||||||
|
@ -69,18 +71,18 @@ impl fmt::Display for HentaiVideo {
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct RawHentaiVideo {
|
pub struct RawHentaiVideo {
|
||||||
pub data: RawHentaiVideoData
|
pub data: RawHentaiVideoData,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct RawHentaiVideoData {
|
pub struct RawHentaiVideoData {
|
||||||
pub captions: RawHentaiVideoSrc,
|
pub captions: RawHentaiVideoSrc,
|
||||||
pub sources: Vec<RawHentaiVideoSrc>
|
pub sources: Vec<RawHentaiVideoSrc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct RawHentaiVideoSrc {
|
pub struct RawHentaiVideoSrc {
|
||||||
pub src: String
|
pub src: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -113,12 +115,10 @@ impl From<serde_json::Error> for Error {
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
formatter.write_str(
|
formatter.write_str(&match self {
|
||||||
&match self {
|
Error::Reqwest(err) => format!("reqwest error: {}", err),
|
||||||
Error::Reqwest(err) => format!("reqwest error: {}", err),
|
Error::QuickXML(err) => format!("quick-xml error: {}", err),
|
||||||
Error::QuickXML(err) => format!("quick-xml error: {}", err),
|
Error::SerdeJSON(err) => format!("serde_json error: {}", err),
|
||||||
Error::SerdeJSON(err) => format!("serde_json error: {}", err),
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
193
src/utils.rs
193
src/utils.rs
|
@ -1,34 +1,46 @@
|
||||||
use crate::structs;
|
use crate::structs;
|
||||||
|
|
||||||
use quick_xml::Reader;
|
|
||||||
use quick_xml::events::Event;
|
use quick_xml::events::Event;
|
||||||
|
use quick_xml::Reader;
|
||||||
extern crate reqwest;
|
extern crate reqwest;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
pub async fn search(client: reqwest::Client, query: &str) -> Result<Vec<structs::SearchResult>, structs::Error> {
|
pub async fn search(
|
||||||
let text = &client.get("https://hentaihaven.xxx/wp-json/wp/v2/wp-manga")
|
client: reqwest::Client,
|
||||||
.query(&[("search", &query)])
|
query: &str,
|
||||||
.send()
|
) -> Result<Vec<structs::SearchResult>, structs::Error> {
|
||||||
.await?
|
let text = &client
|
||||||
.text()
|
.get("https://hentaihaven.xxx/wp-json/wp/v2/wp-manga")
|
||||||
.await?;
|
.query(&[("search", &query)])
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
let text = text.trim_start_matches("\u{feff}");
|
let text = text.trim_start_matches("\u{feff}");
|
||||||
Ok(serde_json::from_str(&text)?)
|
Ok(serde_json::from_str(&text)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_hentai(client: reqwest::Client, id: &str) -> Result<Option<structs::HentaiInfo>, structs::Error> {
|
pub async fn get_hentai(
|
||||||
|
client: reqwest::Client,
|
||||||
|
id: &str,
|
||||||
|
) -> Result<Option<structs::HentaiInfo>, structs::Error> {
|
||||||
let url = match id.contains(|c: char| !c.is_digit(10)) {
|
let url = match id.contains(|c: char| !c.is_digit(10)) {
|
||||||
true => format!("https://hentaihaven.xxx/watch/{}", &id),
|
true => format!("https://hentaihaven.xxx/watch/{}", &id),
|
||||||
false => format!("https://hentaihaven.xxx/?p={}", &id)
|
false => format!("https://hentaihaven.xxx/?p={}", &id),
|
||||||
};
|
};
|
||||||
let resp = client.get(&url)
|
let resp = client.get(&url).send().await?;
|
||||||
.send()
|
|
||||||
.await?;
|
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let mut id = String::new();
|
let mut id = String::new();
|
||||||
let slug = resp.url().path().trim_end_matches('/').rsplitn(2, '/').nth(0).unwrap().to_string();
|
let slug = resp
|
||||||
|
.url()
|
||||||
|
.path()
|
||||||
|
.trim_end_matches('/')
|
||||||
|
.rsplitn(2, '/')
|
||||||
|
.nth(0)
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
let text = resp.text().await?.replace(" ", " ");
|
let text = resp.text().await?.replace(" ", " ");
|
||||||
let mut reader = Reader::from_str(&text);
|
let mut reader = Reader::from_str(&text);
|
||||||
reader.check_end_names(false);
|
reader.check_end_names(false);
|
||||||
|
@ -52,13 +64,10 @@ pub async fn get_hentai(client: reqwest::Client, id: &str) -> Result<Option<stru
|
||||||
match reader.read_event(&mut buf) {
|
match reader.read_event(&mut buf) {
|
||||||
Ok(Event::Start(ref e)) => {
|
Ok(Event::Start(ref e)) => {
|
||||||
if e.name() == b"div" {
|
if e.name() == b"div" {
|
||||||
let class = e.attributes()
|
let class = e.attributes().find(|i| match i.as_ref() {
|
||||||
.find(|i| {
|
Ok(i) => i.key == b"class",
|
||||||
match i.as_ref() {
|
Err(_) => false,
|
||||||
Ok(i) => i.key == b"class",
|
});
|
||||||
Err(_) => false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(class) = class {
|
if let Some(class) = class {
|
||||||
match class.unwrap().unescape_and_decode_value(&reader) {
|
match class.unwrap().unescape_and_decode_value(&reader) {
|
||||||
Ok(class_name) => {
|
Ok(class_name) => {
|
||||||
|
@ -69,80 +78,75 @@ pub async fn get_hentai(client: reqwest::Client, id: &str) -> Result<Option<stru
|
||||||
"nav-links" => is_inside_nav_links = true,
|
"nav-links" => is_inside_nav_links = true,
|
||||||
"listing-chapters_wrap" => is_inside_chapter_list = true,
|
"listing-chapters_wrap" => is_inside_chapter_list = true,
|
||||||
"summary__content show-more" => is_inside_summary = true,
|
"summary__content show-more" => is_inside_summary = true,
|
||||||
_ => ()
|
_ => (),
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
Err(_) => ()
|
Err(_) => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if e.name() == b"a" {
|
} else if e.name() == b"a" {
|
||||||
is_inside_a = true;
|
is_inside_a = true;
|
||||||
if is_inside_nav_links {
|
if is_inside_nav_links {
|
||||||
let class = e.attributes()
|
let class = e.attributes().find(|i| match i.as_ref() {
|
||||||
.find(|i| {
|
Ok(i) => i.key == b"class",
|
||||||
match i.as_ref() {
|
Err(_) => false,
|
||||||
Ok(i) => i.key == b"class",
|
});
|
||||||
Err(_) => false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(class) = class {
|
if let Some(class) = class {
|
||||||
match class.unwrap().unescape_and_decode_value(&reader) {
|
match class.unwrap().unescape_and_decode_value(&reader) {
|
||||||
Ok(class_name) => {
|
Ok(class_name) => {
|
||||||
if class_name.to_lowercase().split_whitespace().any(|i| i == "uncensored") {
|
if class_name
|
||||||
|
.to_lowercase()
|
||||||
|
.split_whitespace()
|
||||||
|
.any(|i| i == "uncensored")
|
||||||
|
{
|
||||||
censored = false;
|
censored = false;
|
||||||
is_inside_nav_links = false;
|
is_inside_nav_links = false;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => ()
|
Err(_) => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if is_inside_chapter_list {
|
} else if is_inside_chapter_list {
|
||||||
let href = e.attributes()
|
let href = e.attributes().find(|i| match i.as_ref() {
|
||||||
.find(|i| {
|
Ok(i) => i.key == b"href",
|
||||||
match i.as_ref() {
|
Err(_) => false,
|
||||||
Ok(i) => i.key == b"href",
|
});
|
||||||
Err(_) => false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(href) = href {
|
if let Some(href) = href {
|
||||||
match href.unwrap().unescape_and_decode_value(&reader) {
|
match href.unwrap().unescape_and_decode_value(&reader) {
|
||||||
Ok(href) => episode_urls.push(href),
|
Ok(href) => episode_urls.push(href),
|
||||||
Err(_) => ()
|
Err(_) => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if id.is_empty() {
|
} else if id.is_empty() {
|
||||||
let data_post = e.attributes()
|
let data_post = e.attributes().find(|i| match i.as_ref() {
|
||||||
.find(|i| {
|
Ok(i) => i.key == b"data-post",
|
||||||
match i.as_ref() {
|
Err(_) => false,
|
||||||
Ok(i) => i.key == b"data-post",
|
});
|
||||||
Err(_) => false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(data_post) = data_post {
|
if let Some(data_post) = data_post {
|
||||||
match data_post.unwrap().unescape_and_decode_value(&reader) {
|
match data_post.unwrap().unescape_and_decode_value(&reader) {
|
||||||
Ok(data_post) => id = data_post,
|
Ok(data_post) => id = data_post,
|
||||||
Err(_) => ()
|
Err(_) => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Ok(Event::Text(e)) => {
|
Ok(Event::Text(e)) => {
|
||||||
let text = match e.unescape_and_decode(&reader) {
|
let text = match e.unescape_and_decode(&reader) {
|
||||||
Ok(text) => text,
|
Ok(text) => text,
|
||||||
Err(_) => continue
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
if is_inside_summary_heading {
|
if is_inside_summary_heading {
|
||||||
match text.trim() {
|
match text.trim() {
|
||||||
"Rank" => to_read_rank = true,
|
"Rank" => to_read_rank = true,
|
||||||
"Genre(s)" => to_read_genres = true,
|
"Genre(s)" => to_read_genres = true,
|
||||||
_ => ()
|
_ => (),
|
||||||
};
|
};
|
||||||
} else if is_inside_summary_content {
|
} else if is_inside_summary_content {
|
||||||
if to_read_rank {
|
if to_read_rank {
|
||||||
match text.trim().splitn(2, " ").nth(0).unwrap().parse::<usize>() {
|
match text.trim().splitn(2, " ").nth(0).unwrap().parse::<usize>() {
|
||||||
Ok(i) => rank = i,
|
Ok(i) => rank = i,
|
||||||
Err(_) => ()
|
Err(_) => (),
|
||||||
};
|
};
|
||||||
to_read_rank = false;
|
to_read_rank = false;
|
||||||
} else if to_read_genres && is_inside_a {
|
} else if to_read_genres && is_inside_a {
|
||||||
|
@ -153,7 +157,7 @@ pub async fn get_hentai(client: reqwest::Client, id: &str) -> Result<Option<stru
|
||||||
} else if is_inside_summary {
|
} else if is_inside_summary {
|
||||||
summary.push_str(&text);
|
summary.push_str(&text);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Ok(Event::End(ref e)) => {
|
Ok(Event::End(ref e)) => {
|
||||||
if e.name() == b"div" {
|
if e.name() == b"div" {
|
||||||
if is_inside_summary_heading {
|
if is_inside_summary_heading {
|
||||||
|
@ -174,10 +178,10 @@ pub async fn get_hentai(client: reqwest::Client, id: &str) -> Result<Option<stru
|
||||||
} else if e.name() == b"a" {
|
} else if e.name() == b"a" {
|
||||||
is_inside_a = false;
|
is_inside_a = false;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(err) => panic!("Error at position {}: {:?}", reader.buffer_position(), err),
|
Err(err) => panic!("Error at position {}: {:?}", reader.buffer_position(), err),
|
||||||
Ok(Event::Eof) => break,
|
Ok(Event::Eof) => break,
|
||||||
_ => ()
|
_ => (),
|
||||||
};
|
};
|
||||||
buf.clear();
|
buf.clear();
|
||||||
}
|
}
|
||||||
|
@ -194,14 +198,15 @@ pub async fn get_hentai(client: reqwest::Client, id: &str) -> Result<Option<stru
|
||||||
genres: genres,
|
genres: genres,
|
||||||
censored: censored,
|
censored: censored,
|
||||||
episode_urls: episode_urls,
|
episode_urls: episode_urls,
|
||||||
summary: summary
|
summary: summary,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_url(client: reqwest::Client, url: &str) -> Result<Option<structs::HentaiVideo>, structs::Error> {
|
pub async fn get_url(
|
||||||
let resp = client.get(url)
|
client: reqwest::Client,
|
||||||
.send()
|
url: &str,
|
||||||
.await?;
|
) -> Result<Option<structs::HentaiVideo>, structs::Error> {
|
||||||
|
let resp = client.get(url).send().await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
@ -213,40 +218,38 @@ pub async fn get_url(client: reqwest::Client, url: &str) -> Result<Option<struct
|
||||||
loop {
|
loop {
|
||||||
match reader.read_event(&mut buf) {
|
match reader.read_event(&mut buf) {
|
||||||
Ok(Event::Start(ref e)) if e.name() == b"iframe" => {
|
Ok(Event::Start(ref e)) if e.name() == b"iframe" => {
|
||||||
let src = e.attributes()
|
let src = e.attributes().find(|i| match i.as_ref() {
|
||||||
.find(|i| {
|
Ok(i) => i.key == b"src",
|
||||||
match i.as_ref() {
|
Err(_) => false,
|
||||||
Ok(i) => i.key == b"src",
|
});
|
||||||
Err(_) => false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(src) = src {
|
if let Some(src) = src {
|
||||||
match src.unwrap().unescape_and_decode_value(&reader) {
|
match src.unwrap().unescape_and_decode_value(&reader) {
|
||||||
Ok(src) => {
|
Ok(src) => {
|
||||||
iframe_url = Some(src);
|
iframe_url = Some(src);
|
||||||
break
|
break;
|
||||||
},
|
}
|
||||||
Err(_) => ()
|
Err(_) => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(err) => panic!("Error at position {}: {:?}", reader.buffer_position(), err),
|
Err(err) => panic!("Error at position {}: {:?}", reader.buffer_position(), err),
|
||||||
Ok(Event::Eof) => break,
|
Ok(Event::Eof) => break,
|
||||||
_ => ()
|
_ => (),
|
||||||
};
|
};
|
||||||
buf.clear();
|
buf.clear();
|
||||||
}
|
}
|
||||||
let iframe_url = match iframe_url {
|
let iframe_url = match iframe_url {
|
||||||
Some(tmp) => tmp,
|
Some(tmp) => tmp,
|
||||||
None => return Ok(None)
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
parse_iframe(client, &iframe_url).await
|
parse_iframe(client, &iframe_url).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn parse_iframe(client: reqwest::Client, url: &str) -> Result<Option<structs::HentaiVideo>, structs::Error> {
|
async fn parse_iframe(
|
||||||
let resp = client.get(url)
|
client: reqwest::Client,
|
||||||
.send()
|
url: &str,
|
||||||
.await?;
|
) -> Result<Option<structs::HentaiVideo>, structs::Error> {
|
||||||
|
let resp = client.get(url).send().await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
@ -261,36 +264,40 @@ async fn parse_iframe(client: reqwest::Client, url: &str) -> Result<Option<struc
|
||||||
Ok(Event::Text(e)) => {
|
Ok(Event::Text(e)) => {
|
||||||
let text = match reader.decode(e.escaped()) {
|
let text = match reader.decode(e.escaped()) {
|
||||||
Ok(text) => text,
|
Ok(text) => text,
|
||||||
Err(_) => continue
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
for i in text.split('\n') {
|
for i in text.split('\n') {
|
||||||
let i = i.trim();
|
let i = i.trim();
|
||||||
if !i.starts_with("data.append('") {
|
if !i.starts_with("data.append('") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut i = i.trim_start_matches("data.append('").trim_end_matches("');").splitn(2, "', '");
|
let mut i = i
|
||||||
|
.trim_start_matches("data.append('")
|
||||||
|
.trim_end_matches("');")
|
||||||
|
.splitn(2, "', '");
|
||||||
let key = match i.next() {
|
let key = match i.next() {
|
||||||
Some(i) => i,
|
Some(i) => i,
|
||||||
None => continue
|
None => continue,
|
||||||
};
|
};
|
||||||
let value = match i.next() {
|
let value = match i.next() {
|
||||||
Some(i) => i,
|
Some(i) => i,
|
||||||
None => continue
|
None => continue,
|
||||||
};
|
};
|
||||||
form = form.text(key.to_string(), value.to_string());
|
form = form.text(key.to_string(), value.to_string());
|
||||||
form_modified = true;
|
form_modified = true;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(err) => panic!("Error at position {}: {:?}", reader.buffer_position(), err),
|
Err(err) => panic!("Error at position {}: {:?}", reader.buffer_position(), err),
|
||||||
Ok(Event::Eof) => break,
|
Ok(Event::Eof) => break,
|
||||||
_ => ()
|
_ => (),
|
||||||
};
|
};
|
||||||
buf.clear();
|
buf.clear();
|
||||||
}
|
}
|
||||||
if !form_modified {
|
if !form_modified {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let text = client.post("https://hentaihaven.xxx/wp-admin/admin-ajax.php")
|
let text = client
|
||||||
|
.post("https://hentaihaven.xxx/wp-admin/admin-ajax.php")
|
||||||
.multipart(form)
|
.multipart(form)
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await?
|
||||||
|
@ -299,17 +306,23 @@ async fn parse_iframe(client: reqwest::Client, url: &str) -> Result<Option<struc
|
||||||
let text = text.trim_start_matches("\u{feff}");
|
let text = text.trim_start_matches("\u{feff}");
|
||||||
let raw_data: structs::RawHentaiVideo = serde_json::from_str(&text)?;
|
let raw_data: structs::RawHentaiVideo = serde_json::from_str(&text)?;
|
||||||
let raw_data = raw_data.data;
|
let raw_data = raw_data.data;
|
||||||
let captions = match client.get(&raw_data.captions.src).send().await?.status().as_u16() {
|
let captions = match client
|
||||||
|
.get(&raw_data.captions.src)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.status()
|
||||||
|
.as_u16()
|
||||||
|
{
|
||||||
200 => Some(raw_data.captions.src),
|
200 => Some(raw_data.captions.src),
|
||||||
_ => None
|
_ => None,
|
||||||
};
|
};
|
||||||
let video_url = match raw_data.sources.get(0) {
|
let video_url = match raw_data.sources.get(0) {
|
||||||
Some(i) => i.src.clone(),
|
Some(i) => i.src.clone(),
|
||||||
None => return Ok(None)
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
Ok(Some(structs::HentaiVideo {
|
Ok(Some(structs::HentaiVideo {
|
||||||
captions: captions,
|
captions: captions,
|
||||||
video: video_url
|
video: video_url,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue