This commit is contained in:
Simon
2025-08-16 18:46:10 +00:00
parent 2248d11d3e
commit 49ca76ab48
3 changed files with 85 additions and 23 deletions

View File

@@ -651,7 +651,7 @@ async fn status(req: HttpRequest) -> Result<impl web::Responder, web::Error> {
status.add_channel(Channel { status.add_channel(Channel {
id: "hentaihaven".to_string(), id: "hentaihaven".to_string(),
name: "Hentaihaven".to_string(), name: "Hentaihaven".to_string(),
description: "Watch free hentai video stream in English subtitles".to_string(), description: "(Work in Progress) Watch free hentai video stream in English subtitles".to_string(),
premium: false, premium: false,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=hentaihaven.xxx".to_string(), favicon: "https://www.google.com/s2/favicons?sz=64&domain=hentaihaven.xxx".to_string(),
status: "active".to_string(), status: "active".to_string(),

View File

@@ -1,3 +1,4 @@
use crate::schema::videos::url;
use crate::util::parse_abbreviated_number; use crate::util::parse_abbreviated_number;
use crate::DbPool; use crate::DbPool;
use crate::providers::Provider; use crate::providers::Provider;
@@ -192,10 +193,10 @@ impl HentaihavenProvider {
.to_vec() .to_vec()
; ;
for video_segment in &raw_videos { for video_segment in &raw_videos {
// let vid = video_segment.split("\n").collect::<Vec<&str>>(); let vid = video_segment.split("\n").collect::<Vec<&str>>();
// for (index, line) in vid.iter().enumerate() { for (index, line) in vid.iter().enumerate() {
// println!("Line {}: {}", index, line); println!("Line {}: {}", index, line);
// } }
let episode_count = video_segment.split("chapter font-meta").collect::<Vec<&str>>()[1] let episode_count = video_segment.split("chapter font-meta").collect::<Vec<&str>>()[1]
.split("class=\"btn-link\">").collect::<Vec<&str>>()[1] .split("class=\"btn-link\">").collect::<Vec<&str>>()[1]
@@ -206,12 +207,24 @@ impl HentaihavenProvider {
.split("class=\"btn-link\">").collect::<Vec<&str>>()[1] .split("class=\"btn-link\">").collect::<Vec<&str>>()[1]
.split("<").collect::<Vec<&str>>()[0] .split("<").collect::<Vec<&str>>()[0]
.split(" ").collect::<Vec<&str>>()[1] == "Season" ; .split(" ").collect::<Vec<&str>>()[1] == "Season" ;
println!("{:?}",video_segment.split("chapter font-meta").collect::<Vec<&str>>()[1]
let url_part = video_segment.split("chapter font-meta").collect::<Vec<&str>>()[1]
.split("href=\"").collect::<Vec<&str>>()[1] .split("href=\"").collect::<Vec<&str>>()[1]
.split("\"").collect::<Vec<&str>>()[0] .split("\"").collect::<Vec<&str>>()[0]
.split("/").collect::<Vec<&str>>()[4]; .split("/").collect::<Vec<&str>>()[4]);
let mut url_part_list = video_segment.split("chapter font-meta").collect::<Vec<&str>>()[1]
.split("href=\"").collect::<Vec<&str>>()[1]
.split("\"").collect::<Vec<&str>>()[0]
.split("/").collect::<Vec<&str>>()[4]
.split("-").collect::<Vec<&str>>();
if url_part_list.len() > 5 {
if let Some(pos) = url_part_list.iter().rposition(|x| *x == "no") {
url_part_list.remove(pos);
}
}
url_part_list.truncate(5);
let url_part = url_part_list.join("-");
for i in 1..=episode_count { for i in 1..=episode_count {
let mut video_url = format!("https://master-lengs.org/api/v3/hh/{}-{}-eng/master.m3u8", url_part, i); let mut video_url = format!("https://master-lengs.org/api/v3/hh/{}-{}-eng/master.m3u8", url_part, i);
if season { if season {
video_url = format!("https://master-lengs.org/api/v3/hh/{}-season-eng/master.m3u8", url_part); video_url = format!("https://master-lengs.org/api/v3/hh/{}-season-eng/master.m3u8", url_part);
@@ -220,10 +233,18 @@ impl HentaihavenProvider {
.split("\"").collect::<Vec<&str>>()[0] .split("\"").collect::<Vec<&str>>()[0]
.to_string(), i); .to_string(), i);
let id = format!("{}-{}", url_part, i); let id = format!("{}-{}", url_part, i);
let mut thumb = format!("https://himg.nl/images/hh/{}-{}-eng/poster.jpg", url_part, i);
if season { let thumb = match video_segment.split("<img").collect::<Vec<&str>>()[1]
thumb = format!("https://himg.nl/images/hh/{}-season-eng/poster.jpg", url_part); .split("").collect::<Vec<&str>>()[0].contains("data-src=\"") {
} true => video_segment.split("<img ").collect::<Vec<&str>>()[1]
.split("data-src=\"").collect::<Vec<&str>>()[1]
.split("\"").collect::<Vec<&str>>()[0]
.to_string(),
false =>video_segment.split("<img ").collect::<Vec<&str>>()[1]
.split("src=\"").collect::<Vec<&str>>()[1]
.split("\"").collect::<Vec<&str>>()[0]
.to_string()
};
items.push(VideoItem::new( items.push(VideoItem::new(
id, id,
title, title,
@@ -231,8 +252,9 @@ impl HentaihavenProvider {
"hentaihaven".to_string(), "hentaihaven".to_string(),
thumb, thumb,
0, // duration is not available 0, // duration is not available
)) )
; .aspect_ratio(0.73)
);
} }
} }
return items; return items;

View File

@@ -60,21 +60,33 @@ impl VideoEmbed {
#[derive(serde::Serialize, Debug, Clone)] #[derive(serde::Serialize, Debug, Clone)]
pub struct VideoItem { pub struct VideoItem {
pub duration: u32, // 110, pub duration: u32, // 110,
#[serde(skip_serializing_if = "Option::is_none")]
pub views: Option<u32>, // 14622653, pub views: Option<u32>, // 14622653,
#[serde(skip_serializing_if = "Option::is_none")]
pub rating: Option<f32>, // 0.0, pub rating: Option<f32>, // 0.0,
pub id: String, // "c85017ca87477168d648727753c4ded8a35f173e22ef93743e707b296becb299", pub id: String, // "c85017ca87477168d648727753c4ded8a35f173e22ef93743e707b296becb299",
pub title: String, // "20 Minutes of Adorable Kittens BEST Compilation", pub title: String, // "20 Minutes of Adorable Kittens BEST Compilation",
pub url: String, // "https://www.youtube.com/watch?v=y0sF5xhGreA", pub url: String, // "https://www.youtube.com/watch?v=y0sF5xhGreA",
pub channel: String, // "youtube", pub channel: String, // "youtube",
pub thumb: String, // "https://i.ytimg.com/vi/y0sF5xhGreA/hqdefault.jpg", pub thumb: String, // "https://i.ytimg.com/vi/y0sF5xhGreA/hqdefault.jpg",
#[serde(skip_serializing_if = "Option::is_none")]
pub uploader: Option<String>, // "The Pet Collective", pub uploader: Option<String>, // "The Pet Collective",
#[serde(skip_serializing_if = "Option::is_none")]
pub uploaderUrl: Option<String>, // "https://www.youtube.com/@petcollective", pub uploaderUrl: Option<String>, // "https://www.youtube.com/@petcollective",
#[serde(skip_serializing_if = "Option::is_none")]
pub verified: Option<bool>, // false, pub verified: Option<bool>, // false,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>, // [], pub tags: Option<Vec<String>>, // [],
#[serde(skip_serializing_if = "Option::is_none")]
pub uploadedAt: Option<u64>, // 1741142954 pub uploadedAt: Option<u64>, // 1741142954
#[serde(skip_serializing_if = "Option::is_none")]
pub formats: Option<Vec<VideoFormat>>, // Additional HTTP headers if needed pub formats: Option<Vec<VideoFormat>>, // Additional HTTP headers if needed
#[serde(skip_serializing_if = "Option::is_none")]
pub embed: Option<VideoEmbed>, // Optional embed information pub embed: Option<VideoEmbed>, // Optional embed information
pub preview: Option<String> #[serde(skip_serializing_if = "Option::is_none")]
pub preview: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub aspectRation: Option<f32>
} }
#[allow(dead_code)] #[allow(dead_code)]
impl VideoItem { impl VideoItem {
@@ -102,7 +114,8 @@ impl VideoItem {
uploadedAt: None, uploadedAt: None,
formats: None, // Placeholder for formats formats: None, // Placeholder for formats
embed: None, // Placeholder for embed information embed: None, // Placeholder for embed information
preview: None preview: None,
aspectRation: None
} }
} }
pub fn tags(mut self, tags: Vec<String>) -> Self { pub fn tags(mut self, tags: Vec<String>) -> Self {
@@ -152,6 +165,11 @@ impl VideoItem {
self.preview = Some(preview); self.preview = Some(preview);
self self
} }
pub fn aspect_ratio(mut self, aspect_ratio: f32) -> Self {
self.aspectRation = Some(aspect_ratio);
self
}
} }
#[derive(serde::Serialize, Debug, Clone)] #[derive(serde::Serialize, Debug, Clone)]
@@ -159,27 +177,49 @@ pub struct VideoFormat {
url: String, url: String,
quality: String, quality: String,
format: String, format: String,
#[serde(skip_serializing_if = "Option::is_none")]
format_id: Option<String>, format_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
format_note: Option<String>, format_note: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
filesize: Option<u32>, filesize: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
asr: Option<u32>, asr: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
fps: Option<u32>, fps: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
width: Option<u32>, width: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
height: Option<u32>, height: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
tbr: Option<u32>, tbr: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
language: Option<String>, language: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
language_preference: Option<u32>, language_preference: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
ext: Option<String>, ext: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
vcodec: Option<String>, vcodec: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
acodec: Option<String>, acodec: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
dynamic_range: Option<String>, dynamic_range: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
abr: Option<u32>, abr: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
vbr: Option<u32>, vbr: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
container: Option<String>, container: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
protocol: Option<String>, protocol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
audio_ext: Option<String>, audio_ext: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
video_ext: Option<String>, video_ext: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
resolution: Option<String>, resolution: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
http_headers: Option<HashMap<String, String>>, http_headers: Option<HashMap<String, String>>,
} }