hanimers/src/structs.rs

252 lines
7.6 KiB
Rust

use std::fmt;
use std::marker::PhantomData;
use std::collections::HashMap;
use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer};
use quick_xml::Reader;
use quick_xml::events::Event;
extern crate reqwest;
extern crate quick_xml;
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
}
impl fmt::Display for SearchResult {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&format!("{}: {}", self.id, &self.name))
}
}
#[derive(Deserialize, Debug)]
pub struct SearchResultMetadata {
pub hits: String
}
#[derive(Deserialize, Debug)]
pub struct HentaiInfo {
pub state: HentaiState
}
#[derive(Deserialize, Debug)]
pub struct HentaiState {
pub data: HentaiData
}
#[derive(Deserialize, Debug)]
pub struct HentaiData {
pub video: HentaiDataVideo
}
#[derive(Deserialize, Debug)]
pub struct HentaiDataVideo {
pub hentai_video: HentaiVideo,
pub videos_manifest: VideosManifest
}
#[derive(Deserialize, Debug)]
pub struct HentaiVideo {
pub id: i32,
pub slug: String,
pub name: String,
#[serde(deserialize_with = "remove_html")]
pub description: String,
pub views: usize,
pub is_hard_subtitled: bool,
pub duration_in_ms: i32,
pub is_censored: bool,
pub likes: usize,
pub dislikes: usize,
pub hentai_tags: Vec<HentaiTag>
}
impl fmt::Display for HentaiInfo {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let video = &self.state.data.video.hentai_video;
let mut text = format!("ID: {}\nSlug: {}\nName: {}\nViews: {}\nLikes: {}\nDislikes: {}\nCensored: {}\nHardsubbed: {}\n",
video.id,
&video.slug,
&video.name,
video.views,
video.likes,
video.dislikes,
match video.is_censored {
true => "Yes",
false => "No"
},
match video.is_hard_subtitled {
true => "Yes",
false => "No"
}
);
let servers = &self.state.data.video.videos_manifest.servers;
if !servers.is_empty() {
let mut string_servers = HashMap::new();
for server in servers {
let mut tmp_string_servers = string_servers.clone();
for stream in &server.streams {
if !stream.url.is_empty() && !tmp_string_servers.contains_key(&stream.height) {
tmp_string_servers.insert(&stream.height, stream.filesize_mbs);
}
}
string_servers.extend(tmp_string_servers);
}
if !string_servers.is_empty() {
let mut int_servers = HashMap::with_capacity(string_servers.len());
for (i, j) in &string_servers {
match i.parse::<i32>() {
Ok(i) => int_servers.insert(i, j),
Err(_) => {
int_servers.clear();
break;
}
};
}
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(", ")
},
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(", ")
}
}
));
}
}
if video.duration_in_ms > 0 {
let mut seconds = video.duration_in_ms / 1000;
let mut minutes = seconds / 60;
if minutes > 0 {
seconds -= minutes * 60;
}
let hours = minutes / 60;
if hours > 0 {
minutes -= hours * 60;
seconds -= hours * 60 * 60;
}
let mut duration = String::new();
if hours > 0 {
duration.push_str(&format!("{}:", hours));
}
if minutes > 0 {
duration.push_str(&format!("{}:", minutes));
}
duration.push_str(&seconds.to_string());
text.push_str(&format!("Duration: {}\n", &duration));
}
formatter.write_str(&format!("{}Tags: {}\nDescription: {}", &text, video.hentai_tags.iter().map(|i| i.text.as_str()).collect::<Vec<_>>().join(", "), &video.description))
}
}
#[derive(Deserialize, Debug)]
pub struct HentaiTag {
pub id: i32,
pub text: String
}
#[derive(Deserialize, Debug)]
pub struct VideosManifest {
pub servers: Vec<VideoServer>
}
#[derive(Deserialize, Debug)]
pub struct VideoServer {
pub is_permanent: bool,
pub streams: Vec<VideoStream>
}
#[derive(Deserialize, Debug)]
pub struct VideoStream {
pub height: String,
pub url: String,
pub filesize_mbs: i32
}
fn remove_html<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>
{
struct RemoveHTML<T>(PhantomData<fn() -> T>);
impl<'de> Visitor<'de> for RemoveHTML<String>
{
type Value = String;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("HTML string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error
{
// https://brokenco.de/2020/08/03/serde-deserialize-with-string.html
let mut to_return = String::new();
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)) => to_return.push_str(&e.unescape_and_decode(&reader).map_err(de::Error::custom)?),
Ok(Event::Eof) => break,
Err(err) => panic!("Error at position {}: {:?}", reader.buffer_position(), err),
_ => ()
};
buf.clear();
}
Ok(to_return)
}
}
deserializer.deserialize_any(RemoveHTML(PhantomData))
}
#[derive(Debug)]
pub enum Error {
Reqwest(reqwest::Error),
QuickXML(quick_xml::Error),
SerdeJSON(serde_json::Error),
}
impl From<reqwest::Error> for Error {
#[inline]
fn from(error: reqwest::Error) -> Error {
Error::Reqwest(error)
}
}
impl From<quick_xml::Error> for Error {
#[inline]
fn from(error: quick_xml::Error) -> Error {
Error::QuickXML(error)
}
}
impl From<serde_json::Error> for Error {
#[inline]
fn from(error: serde_json::Error) -> Error {
Error::SerdeJSON(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),
}
)
}
}