use crate::api::ClientVersion; use crate::DbPool; use crate::providers::Provider; use crate::status::*; use crate::util::cache::VideoCache; use crate::util::discord::format_error_chain; use crate::util::discord::send_discord_error_report; use crate::util::requester::Requester; use crate::util::time::parse_time_to_seconds; use crate::videos::ServerOptions; use crate::videos::VideoItem; use async_trait::async_trait; use error_chain::error_chain; use htmlentity::entity::{ICodedDataTrait, decode}; use scraper::{Html, Selector}; use std::vec; error_chain! { foreign_links { Io(std::io::Error); HttpRequest(wreq::Error); JsonError(serde_json::Error); } errors { Parse(msg: String) { description("html parse error") display("html parse error: {}", msg) } } } #[derive(Debug, Clone)] pub struct SxyprnProvider { url: String, } impl SxyprnProvider { pub fn new() -> Self { SxyprnProvider { url: "https://sxyprn.com".to_string(), } } fn build_channel(&self, _clientversion: ClientVersion) -> Channel { Channel { id: "sxyprn".to_string(), name: "SexyPorn".to_string(), description: "Free Porn Site".to_string(), premium: false, favicon: "https://www.google.com/s2/favicons?sz=64&domain=sxyprn.com".to_string(), status: "active".to_string(), categories: vec![], options: vec![ ChannelOption { id: "sort".to_string(), title: "Sort".to_string(), description: "Sort the Videos".to_string(), systemImage: "list.number".to_string(), colorName: "blue".to_string(), options: vec![ FilterOption { id: "latest".to_string(), title: "Latest".to_string(), }, FilterOption { id: "views".to_string(), title: "Views".to_string(), }, FilterOption { id: "rating".to_string(), title: "Rating".to_string(), }, FilterOption { id: "orgasmic".to_string(), title: "Orgasmic".to_string(), }, ], multiSelect: false, }, ChannelOption { id: "filter".to_string(), title: "Filter".to_string(), description: "Filter the Videos".to_string(), systemImage: "line.horizontal.3.decrease.circle".to_string(), colorName: "green".to_string(), options: vec![ FilterOption { id: "top".to_string(), title: "Top".to_string(), }, FilterOption { id: "other".to_string(), title: "Other".to_string(), }, FilterOption { id: "all".to_string(), title: "All".to_string(), }, ], multiSelect: false, }, ], nsfw: true, cacheDuration: Some(1800), } } async fn get( &self, cache: VideoCache, pool: DbPool, page: u8, sort: String, options: ServerOptions, ) -> Result> { let sort_string = match sort.as_str() { "views" => "views", "rating" => "rating", "orgasmic" => "orgasmic", _ => "latest", }; // Extract needed fields from options at the start let filter = options.filter.clone().unwrap_or_else(|| "top".to_string()); let filter_string = match filter.as_str() { "other" => "other", "all" => "all", _ => "top", }; let mut requester = crate::providers::requester_or_default(&options, module_path!(), "missing_requester"); let url_str = format!( "{}/blog/all/{}.html?fl={}&sm={}", self.url, ((page as u32) - 1) * 20, filter_string, sort_string ); let old_items = match cache.get(&url_str) { Some((time, items)) => { if time.elapsed().unwrap_or_default().as_secs() < 60 * 60 { return Ok(items.clone()); } else { items.clone() } } None => { vec![] } }; let text = match requester.get(&url_str, None).await { Ok(text) => text, Err(e) => { crate::providers::report_provider_error( "sxyprn", "get.request", &format!("url={url_str}; error={e}"), ) .await; return Ok(old_items); } }; // Pass a reference to options if needed, or reconstruct as needed let video_items = match self .get_video_items_from_html(text.clone(), pool, requester) .await { Ok(items) => items, Err(e) => { println!("Error parsing video items: {}", e); send_discord_error_report( e.to_string(), Some(format_error_chain(&e)), Some("Sxyprn Provider"), Some(&format!("URL: {}", url_str)), file!(), line!(), module_path!(), ).await; return Ok(old_items); } }; // let video_items: Vec = self // .get_video_items_from_html(text.clone(), pool, requester) // .await; if !video_items.is_empty() { cache.remove(&url_str); cache.insert(url_str.clone(), video_items.clone()); } else { return Ok(old_items); } Ok(video_items) } async fn query( &self, cache: VideoCache, pool: DbPool, page: u8, query: &str, sort: String, options: ServerOptions, ) -> Result> { let sort_string = match sort.as_str() { "views" => "views", "rating" => "trending", "orgasmic" => "orgasmic", _ => "latest", }; // Extract needed fields from options at the start let mut requester = crate::providers::requester_or_default(&options, module_path!(), "missing_requester"); let search_string = query.replace(" ", "-"); let url_str = format!( "{}/{}.html?page={}&sm={}", self.url, search_string, ((page as u32) - 1) * 20, sort_string ); // Check our Video Cache. If the result is younger than 1 hour, we return it. let old_items = match cache.get(&url_str) { Some((time, items)) => { if time.elapsed().unwrap_or_default().as_secs() < 60 * 60 { return Ok(items.clone()); } else { let _ = cache.check().await; return Ok(items.clone()); } } None => { vec![] } }; let text = match requester.get(&url_str, None).await { Ok(text) => text, Err(e) => { crate::providers::report_provider_error( "sxyprn", "query.request", &format!("url={url_str}; error={e}"), ) .await; return Ok(old_items); } }; let video_items = match self .get_video_items_from_html(text.clone(), pool, requester) .await { Ok(items) => items, Err(e) => { println!("Error parsing video items: {}", e);// 1. Convert the error to a string immediately send_discord_error_report( e.to_string(), Some(format_error_chain(&e)), Some("Sxyprn Provider"), Some(&format!("URL: {}", url_str)), file!(), line!(), module_path!(), ).await; return Ok(old_items); } }; // let video_items: Vec = self // .get_video_items_from_html(text.clone(), pool, requester) // .await; if !video_items.is_empty() { cache.remove(&url_str); cache.insert(url_str.clone(), video_items.clone()); } else { return Ok(old_items); } Ok(video_items) } async fn get_video_items_from_html( &self, html: String, _pool: DbPool, _requester: Requester, ) -> Result> { if html.is_empty() { return Ok(vec![]); } // take content before "