|
|
|
@ -1,20 +1,20 @@
|
|
|
|
|
use std::fmt;
|
|
|
|
|
use std::marker::PhantomData;
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use crate::unescape::unescape;
|
|
|
|
|
use quick_xml::events::Event;
|
|
|
|
|
use quick_xml::Reader;
|
|
|
|
|
use serde::de::{self, Visitor};
|
|
|
|
|
use serde::{Deserialize, Deserializer};
|
|
|
|
|
use quick_xml::Reader;
|
|
|
|
|
use quick_xml::events::Event;
|
|
|
|
|
use crate::unescape::unescape;
|
|
|
|
|
extern crate reqwest;
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use std::fmt;
|
|
|
|
|
use std::marker::PhantomData;
|
|
|
|
|
extern crate quick_xml;
|
|
|
|
|
extern crate reqwest;
|
|
|
|
|
extern crate serde_json;
|
|
|
|
|
|
|
|
|
|
// contains more info that i won't add for search results, maybe non_exhaustive? not sure
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
|
pub struct SearchResult {
|
|
|
|
|
pub id: i32,
|
|
|
|
|
pub name: String
|
|
|
|
|
pub name: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for SearchResult {
|
|
|
|
@ -25,28 +25,28 @@ impl fmt::Display for SearchResult {
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
|
pub struct SearchResultMetadata {
|
|
|
|
|
pub hits: String
|
|
|
|
|
pub hits: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
|
pub struct HentaiInfo {
|
|
|
|
|
pub state: HentaiState
|
|
|
|
|
pub state: HentaiState,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
|
pub struct HentaiState {
|
|
|
|
|
pub data: HentaiData
|
|
|
|
|
pub data: HentaiData,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
|
pub struct HentaiData {
|
|
|
|
|
pub video: HentaiDataVideo
|
|
|
|
|
pub video: HentaiDataVideo,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
|
pub struct HentaiDataVideo {
|
|
|
|
|
pub hentai_video: HentaiVideo,
|
|
|
|
|
pub videos_manifest: VideosManifest
|
|
|
|
|
pub videos_manifest: VideosManifest,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
@ -62,7 +62,7 @@ pub struct HentaiVideo {
|
|
|
|
|
pub is_censored: bool,
|
|
|
|
|
pub likes: usize,
|
|
|
|
|
pub dislikes: usize,
|
|
|
|
|
pub hentai_tags: Vec<HentaiTag>
|
|
|
|
|
pub hentai_tags: Vec<HentaiTag>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for HentaiInfo {
|
|
|
|
@ -107,17 +107,24 @@ impl fmt::Display for HentaiInfo {
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
text.push_str(&format!("Resolution: {}\n",
|
|
|
|
|
text.push_str(&format!(
|
|
|
|
|
"Resolution: {}\n",
|
|
|
|
|
match int_servers.is_empty() {
|
|
|
|
|
true => {
|
|
|
|
|
let mut keys: Vec<&&String> = string_servers.keys().collect();
|
|
|
|
|
keys.sort();
|
|
|
|
|
keys.iter().map(|&i| format!("{} ({}MB)", i, string_servers.get(i).unwrap())).collect::<Vec<_>>().join(", ")
|
|
|
|
|
},
|
|
|
|
|
keys.iter()
|
|
|
|
|
.map(|&i| format!("{} ({}MB)", i, string_servers.get(i).unwrap()))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ")
|
|
|
|
|
}
|
|
|
|
|
false => {
|
|
|
|
|
let mut keys: Vec<&i32> = int_servers.keys().collect();
|
|
|
|
|
keys.sort();
|
|
|
|
|
keys.iter().map(|i| format!("{} ({}MB)", i, int_servers.get(i).unwrap())).collect::<Vec<_>>().join(", ")
|
|
|
|
|
keys.iter()
|
|
|
|
|
.map(|i| format!("{} ({}MB)", i, int_servers.get(i).unwrap()))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
@ -143,7 +150,15 @@ impl fmt::Display for HentaiInfo {
|
|
|
|
|
}
|
|
|
|
|
text.push_str(&format!("Duration: {}{:02}\n", &duration, seconds));
|
|
|
|
|
}
|
|
|
|
|
text.push_str(&format!("Tags: {}", video.hentai_tags.iter().map(|i| i.text.as_str()).collect::<Vec<_>>().join(", ")));
|
|
|
|
|
text.push_str(&format!(
|
|
|
|
|
"Tags: {}",
|
|
|
|
|
video
|
|
|
|
|
.hentai_tags
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|i| i.text.as_str())
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ")
|
|
|
|
|
));
|
|
|
|
|
if !video.description.is_empty() {
|
|
|
|
|
text.push_str(&format!("\nDescription: {}", &video.description));
|
|
|
|
|
}
|
|
|
|
@ -154,35 +169,34 @@ impl fmt::Display for HentaiInfo {
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
|
pub struct HentaiTag {
|
|
|
|
|
pub id: i32,
|
|
|
|
|
pub text: String
|
|
|
|
|
pub text: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
|
pub struct VideosManifest {
|
|
|
|
|
pub servers: Vec<VideoServer>
|
|
|
|
|
pub servers: Vec<VideoServer>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
|
pub struct VideoServer {
|
|
|
|
|
pub is_permanent: bool,
|
|
|
|
|
pub streams: Vec<VideoStream>
|
|
|
|
|
pub streams: Vec<VideoStream>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
|
pub struct VideoStream {
|
|
|
|
|
pub height: String,
|
|
|
|
|
pub url: String,
|
|
|
|
|
pub filesize_mbs: i32
|
|
|
|
|
pub filesize_mbs: i32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn remove_html<'de, D>(deserializer: D) -> Result<String, D::Error>
|
|
|
|
|
where
|
|
|
|
|
D: Deserializer<'de>
|
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
|
{
|
|
|
|
|
struct RemoveHTML<T>(PhantomData<fn() -> T>);
|
|
|
|
|
|
|
|
|
|
impl<'de> Visitor<'de> for RemoveHTML<String>
|
|
|
|
|
{
|
|
|
|
|
impl<'de> Visitor<'de> for RemoveHTML<String> {
|
|
|
|
|
type Value = String;
|
|
|
|
|
|
|
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
@ -191,19 +205,23 @@ where
|
|
|
|
|
|
|
|
|
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
|
|
|
|
where
|
|
|
|
|
E: de::Error
|
|
|
|
|
E: de::Error,
|
|
|
|
|
{
|
|
|
|
|
// https://brokenco.de/2020/08/03/serde-deserialize-with-string.html
|
|
|
|
|
// https://brokenco.de/2020/08/03/serde-deserialize-with-string.html
|
|
|
|
|
let mut text = String::with_capacity(value.len());
|
|
|
|
|
let mut reader = Reader::from_str(value);
|
|
|
|
|
reader.check_end_names(false);
|
|
|
|
|
let mut buf = Vec::new();
|
|
|
|
|
loop {
|
|
|
|
|
match reader.read_event(&mut buf) {
|
|
|
|
|
Ok(Event::Text(e)) => text.push_str(&unescape(reader.decode(e.escaped()).map_err(serde::de::Error::custom)?)),
|
|
|
|
|
Ok(Event::Text(e)) => text.push_str(&unescape(
|
|
|
|
|
reader
|
|
|
|
|
.decode(e.escaped())
|
|
|
|
|
.map_err(serde::de::Error::custom)?,
|
|
|
|
|
)),
|
|
|
|
|
Ok(Event::Eof) => break,
|
|
|
|
|
Err(err) => panic!("Error at position {}: {:?}", reader.buffer_position(), err),
|
|
|
|
|
_ => ()
|
|
|
|
|
_ => (),
|
|
|
|
|
};
|
|
|
|
|
buf.clear();
|
|
|
|
|
}
|
|
|
|
@ -244,12 +262,10 @@ impl From<serde_json::Error> for Error {
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for Error {
|
|
|
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
|
formatter.write_str(
|
|
|
|
|
&match self {
|
|
|
|
|
Error::Reqwest(err) => format!("reqwest error: {}", err),
|
|
|
|
|
Error::QuickXML(err) => format!("quick-xml error: {}", err),
|
|
|
|
|
Error::SerdeJSON(err) => format!("serde_json error: {}", err),
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
formatter.write_str(&match self {
|
|
|
|
|
Error::Reqwest(err) => format!("reqwest error: {}", err),
|
|
|
|
|
Error::QuickXML(err) => format!("quick-xml error: {}", err),
|
|
|
|
|
Error::SerdeJSON(err) => format!("serde_json error: {}", err),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|