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

@@ -648,10 +648,10 @@ async fn status(req: HttpRequest) -> Result<impl web::Responder, web::Error> {
nsfw: true,
});
status.add_channel(Channel {
status.add_channel(Channel {
id: "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,
favicon: "https://www.google.com/s2/favicons?sz=64&domain=hentaihaven.xxx".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::DbPool;
use crate::providers::Provider;
@@ -192,10 +193,10 @@ impl HentaihavenProvider {
.to_vec()
;
for video_segment in &raw_videos {
// let vid = video_segment.split("\n").collect::<Vec<&str>>();
// for (index, line) in vid.iter().enumerate() {
// println!("Line {}: {}", index, line);
// }
let vid = video_segment.split("\n").collect::<Vec<&str>>();
for (index, line) in vid.iter().enumerate() {
println!("Line {}: {}", index, line);
}
let episode_count = video_segment.split("chapter font-meta").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("<").collect::<Vec<&str>>()[0]
.split(" ").collect::<Vec<&str>>()[1] == "Season" ;
let url_part = video_segment.split("chapter font-meta").collect::<Vec<&str>>()[1]
println!("{:?}",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>>()[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 {
let mut video_url = format!("https://master-lengs.org/api/v3/hh/{}-{}-eng/master.m3u8", url_part, i);
if season {
video_url = format!("https://master-lengs.org/api/v3/hh/{}-season-eng/master.m3u8", url_part);
@@ -220,19 +233,28 @@ impl HentaihavenProvider {
.split("\"").collect::<Vec<&str>>()[0]
.to_string(), i);
let id = format!("{}-{}", url_part, i);
let mut thumb = format!("https://himg.nl/images/hh/{}-{}-eng/poster.jpg", url_part, i);
if season {
thumb = format!("https://himg.nl/images/hh/{}-season-eng/poster.jpg", url_part);
}
let thumb = match video_segment.split("<img").collect::<Vec<&str>>()[1]
.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(
id,
title,
video_url,
"hentaihaven".to_string(),
thumb,
0, // duration is not available
))
;
id,
title,
video_url,
"hentaihaven".to_string(),
thumb,
0, // duration is not available
)
.aspect_ratio(0.73)
);
}
}
return items;

View File

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