From 437deb388b21e7280c76178c16b0d430f74474d8 Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 31 Aug 2025 17:16:37 +0000 Subject: [PATCH] noodlemagazine --- src/api.rs | 14 +++ src/providers/mod.rs | 6 + src/providers/noodlemagazine.rs | 196 ++++++++++++++++++++++++++++++++ src/providers/porn00.rs | 2 +- 4 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 src/providers/noodlemagazine.rs diff --git a/src/api.rs b/src/api.rs index 359a6e8..95d4dbd 100644 --- a/src/api.rs +++ b/src/api.rs @@ -757,6 +757,19 @@ async fn status(req: HttpRequest) -> Result { nsfw: true, }); + // porn00 + status.add_channel(Channel { + id: "noodlemagazine".to_string(), + name: "Noodlemagazine".to_string(), + description: "Discover the Best Adult Videos".to_string(), + premium: false, + favicon: "https://www.google.com/s2/favicons?sz=64&domain=noodlemagazine.com".to_string(), + status: "active".to_string(), + categories: vec![], + options: vec![], + nsfw: true, + }); + //missav status.add_channel(Channel { id: "missav".to_string(), @@ -1033,6 +1046,7 @@ pub fn get_provider(channel: &str) -> Option { "xxthots" => Some(AnyProvider::Xxthots(crate::providers::xxthots::XxthotsProvider::new())), "sxyprn" => Some(AnyProvider::Sxyprn(crate::providers::sxyprn::SxyprnProvider::new())), "porn00" => Some(AnyProvider::Porn00(crate::providers::porn00::Porn00Provider::new())), + "noodlemagazine" => Some(AnyProvider::Noodlemagazine(crate::providers::noodlemagazine::NoodlemagazineProvider::new())), _ => Some(AnyProvider::Perverzija(PerverzijaProvider::new())), } } diff --git a/src/providers/mod.rs b/src/providers/mod.rs index f006e13..561e2e0 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -22,6 +22,7 @@ pub mod missav; pub mod xxthots; pub mod sxyprn; pub mod porn00; +pub mod noodlemagazine; pub trait Provider { async fn get_videos( @@ -56,6 +57,7 @@ pub enum AnyProvider { Xxthots(crate::providers::xxthots::XxthotsProvider), Sxyprn(crate::providers::sxyprn::SxyprnProvider), Porn00(crate::providers::porn00::Porn00Provider), + Noodlemagazine(crate::providers::noodlemagazine::NoodlemagazineProvider), } impl Provider for AnyProvider { @@ -154,6 +156,10 @@ impl Provider for AnyProvider { p.get_videos(cache, pool, sort, query, page, per_page, options,) .await } + AnyProvider::Noodlemagazine(p) => { + p.get_videos(cache, pool, sort, query, page, per_page, options,) + .await + } } } } diff --git a/src/providers/noodlemagazine.rs b/src/providers/noodlemagazine.rs new file mode 100644 index 0000000..fd84eb5 --- /dev/null +++ b/src/providers/noodlemagazine.rs @@ -0,0 +1,196 @@ +use crate::util::parse_abbreviated_number; +use crate::DbPool; +use crate::providers::Provider; +use crate::util::cache::VideoCache; +use crate::util::flaresolverr::{FlareSolverrRequest, Flaresolverr}; +use crate::util::time::parse_time_to_seconds; +use crate::videos::{ServerOptions, VideoItem}; +use error_chain::error_chain; +use htmlentity::entity::{ICodedDataTrait, decode}; +use std::env; +use std::vec; +use wreq::{Client, Proxy}; +use wreq_util::Emulation; + +error_chain! { + foreign_links { + Io(std::io::Error); + HttpRequest(wreq::Error); + } +} + +#[derive(Debug, Clone)] +pub struct NoodlemagazineProvider { + url: String, +} +impl NoodlemagazineProvider { + pub fn new() -> Self { + NoodlemagazineProvider { + url: "https://noodlemagazine.com".to_string(), + } + } + async fn get( + &self, + cache: VideoCache, + page: u8, + options: ServerOptions, + ) -> Result> { + + let video_url = format!("{}/popular/recent?p={}", self.url, page-1); + let old_items = match cache.get(&video_url) { + Some((time, items)) => { + if time.elapsed().unwrap_or_default().as_secs() < 60 * 5 { + println!("Cache hit for URL: {}", video_url); + return Ok(items.clone()); + } else { + items.clone() + } + } + None => { + vec![] + } + }; + + let mut requester = options.requester.clone().unwrap(); + + let text = requester.get(&video_url).await.unwrap(); + let video_items: Vec = self.get_video_items_from_html(text.clone()); + if !video_items.is_empty() { + cache.remove(&video_url); + cache.insert(video_url.clone(), video_items.clone()); + } else { + return Ok(old_items); + } + Ok(video_items) + } + + async fn query( + &self, + cache: VideoCache, + page: u8, + query: &str, + options: ServerOptions, + ) -> Result> { + let video_url = format!("{}/video/{}?p={}", self.url, query, page); + // Check our Video Cache. If the result is younger than 1 hour, we return it. + let old_items = match cache.get(&video_url) { + Some((time, items)) => { + if time.elapsed().unwrap_or_default().as_secs() < 60 * 5 { + return Ok(items.clone()); + } else { + let _ = cache.check().await; + return Ok(items.clone()); + } + } + None => { + vec![] + } + }; + + let mut requester = options.requester.clone().unwrap(); + + let text = requester.get(&video_url).await.unwrap(); + let video_items: Vec = self.get_video_items_from_html(text.clone()); + if !video_items.is_empty() { + cache.remove(&video_url); + cache.insert(video_url.clone(), video_items.clone()); + } else { + return Ok(old_items); + } + Ok(video_items) + } + + fn get_video_items_from_html(&self, html: String) -> Vec { + if html.is_empty() { + println!("HTML is empty"); + return vec![]; + } + let mut items: Vec = Vec::new(); + let raw_videos = html.split("class=\"more\"").collect::>()[0] + .split("
") + .collect::>()[1..] + .to_vec(); + for video_segment in &raw_videos { + // let vid = video_segment.split("\n").collect::>(); + // for (index, line) in vid.iter().enumerate() { + // println!("Line {}: {}", index, line); + // } + let video_url: String = video_segment.split(">()[1] + .split("\"") + .collect::>()[0].to_string(); + let mut title = video_segment.split("\"title\">").collect::>()[1] + .split("<") + .collect::>()[0] + .to_string(); + // html decode + title = decode(title.as_bytes()).to_string().unwrap_or(title); + let id = video_url.split("/").collect::>()[4].to_string(); + let raw_duration = video_segment.split("#clock-o").collect::>()[1] + .split("").collect::>()[1] + .split("<").collect::>()[0] + .to_string(); + let duration = parse_time_to_seconds(&raw_duration).unwrap_or(0) as u32; + + let thumb = video_segment.split(">()[1] + .split("data-src=\"").collect::>()[1] + .split("\"") + .collect::>()[0] + .to_string(); + + let views_part = video_segment.split("#eye").collect::>()[1] + .split("").collect::>()[1] + .split("<").collect::>()[0] + .to_string(); + let duration = parse_time_to_seconds(&raw_duration).unwrap_or(0) as u32; + let views = parse_abbreviated_number(&views_part).unwrap_or(0) as u32; + + let video_item = VideoItem::new( + id, + title, + video_url.to_string(), + "noodlemagazine".to_string(), + thumb, + duration, + ) + .views(views) + ; + items.push(video_item); + } + return items; + } + + +} + +impl Provider for NoodlemagazineProvider { + async fn get_videos( + &self, + cache: VideoCache, + pool: DbPool, + sort: String, + query: Option, + page: String, + per_page: String, + options: ServerOptions, + ) -> Vec { + let _ = per_page; + let _ = pool; + let videos: std::result::Result, Error> = match query { + Some(q) => { + self.query(cache, page.parse::().unwrap_or(1), &q,options) + .await + } + None => { + self.get(cache, page.parse::().unwrap_or(1), options) + .await + } + }; + match videos { + Ok(v) => v, + Err(e) => { + println!("Error fetching videos: {}", e); + vec![] + } + } + } +} diff --git a/src/providers/porn00.rs b/src/providers/porn00.rs index 568d711..e777ac3 100644 --- a/src/providers/porn00.rs +++ b/src/providers/porn00.rs @@ -84,7 +84,7 @@ impl Porn00Provider { options: ServerOptions, ) -> Result> { let search_string = query.to_lowercase().trim().replace(" ", "-"); - let video_url = format!("{}/search/{}/?mode=async&function=get_block&block_id=list_videos_videos_list_search_result&q={}&category_ids=&sort_by=post_date&from_videos={}&from_albums={}&", self.url, search_string, search_string, page, page); + let video_url = format!("{}/q/{}/?mode=async&function=get_block&block_id=list_videos_videos_list_search_result&q={}&category_ids=&sort_by=post_date&from_videos={}&from_albums={}&", self.url, search_string, search_string, page, page); // Check our Video Cache. If the result is younger than 1 hour, we return it. let old_items = match cache.get(&video_url) { Some((time, items)) => {