redtube
This commit is contained in:
13
src/api.rs
13
src/api.rs
@@ -8,6 +8,7 @@ use crate::providers::hanime::HanimeProvider;
|
|||||||
use crate::providers::perverzija::PerverzijaProvider;
|
use crate::providers::perverzija::PerverzijaProvider;
|
||||||
use crate::providers::pmvhaven::PmvhavenProvider;
|
use crate::providers::pmvhaven::PmvhavenProvider;
|
||||||
use crate::providers::pornhub::PornhubProvider;
|
use crate::providers::pornhub::PornhubProvider;
|
||||||
|
use crate::providers::redtube::RedtubeProvider;
|
||||||
use crate::providers::rule34video::Rule34videoProvider;
|
use crate::providers::rule34video::Rule34videoProvider;
|
||||||
use crate::providers::spankbang::SpankbangProvider;
|
use crate::providers::spankbang::SpankbangProvider;
|
||||||
use crate::util::cache::VideoCache;
|
use crate::util::cache::VideoCache;
|
||||||
@@ -429,6 +430,17 @@ async fn status(req: HttpRequest) -> Result<impl web::Responder, web::Error> {
|
|||||||
multiSelect: false,
|
multiSelect: false,
|
||||||
}],
|
}],
|
||||||
nsfw: true,
|
nsfw: true,
|
||||||
|
});
|
||||||
|
status.add_channel(Channel {
|
||||||
|
id: "redtube".to_string(),
|
||||||
|
name: "Redtube".to_string(),
|
||||||
|
description: "Redtube brings you NEW porn videos every day for free. Enjoy our XXX movies in high quality HD resolution on any device. Get fully immersed with the latest virtual reality sex videos from top adult studios. Stream all of the hottest porn movies from your favorite categories, pornstars and porn channels !".to_string(),
|
||||||
|
premium: false,
|
||||||
|
favicon: "https://www.google.com/s2/favicons?sz=64&domain=www.redtube.com".to_string(),
|
||||||
|
status: "active".to_string(),
|
||||||
|
categories: vec![],
|
||||||
|
options: vec![],
|
||||||
|
nsfw: true,
|
||||||
});
|
});
|
||||||
status.iconUrl = format!("http://{}/favicon.ico", host).to_string();
|
status.iconUrl = format!("http://{}/favicon.ico", host).to_string();
|
||||||
Ok(web::HttpResponse::Ok().json(&status))
|
Ok(web::HttpResponse::Ok().json(&status))
|
||||||
@@ -545,6 +557,7 @@ pub fn get_provider(channel: &str) -> Option<AnyProvider> {
|
|||||||
"pornhub" => Some(AnyProvider::Pornhub(PornhubProvider::new())),
|
"pornhub" => Some(AnyProvider::Pornhub(PornhubProvider::new())),
|
||||||
"pmvhaven" => Some(AnyProvider::Pmvhaven(PmvhavenProvider::new())),
|
"pmvhaven" => Some(AnyProvider::Pmvhaven(PmvhavenProvider::new())),
|
||||||
"rule34video" => Some(AnyProvider::Rule34video(Rule34videoProvider::new())),
|
"rule34video" => Some(AnyProvider::Rule34video(Rule34videoProvider::new())),
|
||||||
|
"redtube" => Some(AnyProvider::Redtube(RedtubeProvider::new())),
|
||||||
|
|
||||||
_ => Some(AnyProvider::Perverzija(PerverzijaProvider::new())),
|
_ => Some(AnyProvider::Perverzija(PerverzijaProvider::new())),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
providers::{
|
providers::{
|
||||||
hanime::HanimeProvider, perverzija::PerverzijaProvider, pmvhaven::PmvhavenProvider, pornhub::PornhubProvider, rule34video::Rule34videoProvider, spankbang::SpankbangProvider
|
hanime::HanimeProvider, perverzija::PerverzijaProvider, pmvhaven::PmvhavenProvider, pornhub::PornhubProvider, rule34video::Rule34videoProvider, spankbang::SpankbangProvider, redtube::RedtubeProvider,
|
||||||
}, util::cache::VideoCache, videos::VideoItem, DbPool
|
}, util::cache::VideoCache, videos::VideoItem, DbPool
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ pub mod pmvhaven;
|
|||||||
pub mod pornhub;
|
pub mod pornhub;
|
||||||
pub mod spankbang;
|
pub mod spankbang;
|
||||||
pub mod rule34video;
|
pub mod rule34video;
|
||||||
|
pub mod redtube;
|
||||||
|
|
||||||
pub trait Provider {
|
pub trait Provider {
|
||||||
async fn get_videos(
|
async fn get_videos(
|
||||||
@@ -34,6 +35,7 @@ pub enum AnyProvider {
|
|||||||
Pornhub(PornhubProvider),
|
Pornhub(PornhubProvider),
|
||||||
Pmvhaven(PmvhavenProvider),
|
Pmvhaven(PmvhavenProvider),
|
||||||
Rule34video(Rule34videoProvider),
|
Rule34video(Rule34videoProvider),
|
||||||
|
Redtube(RedtubeProvider), // Assuming Redtube is similar to Rule34video
|
||||||
}
|
}
|
||||||
impl Provider for AnyProvider {
|
impl Provider for AnyProvider {
|
||||||
async fn get_videos(
|
async fn get_videos(
|
||||||
@@ -92,6 +94,11 @@ impl Provider for AnyProvider {
|
|||||||
category.clone(),)
|
category.clone(),)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
AnyProvider::Redtube(p) => {
|
||||||
|
p.get_videos(cache, pool, channel, sort, query, page, per_page, featured,
|
||||||
|
category.clone(),)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
295
src/providers/redtube.rs
Normal file
295
src/providers/redtube.rs
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
use crate::schema::videos::url;
|
||||||
|
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::{VideoItem};
|
||||||
|
use error_chain::error_chain;
|
||||||
|
use futures::stream::SplitSink;
|
||||||
|
use htmlentity::entity::{ICodedDataTrait, decode};
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::env;
|
||||||
|
use std::os::linux::raw;
|
||||||
|
use std::time::Duration;
|
||||||
|
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 RedtubeProvider {
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
impl RedtubeProvider {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
RedtubeProvider {
|
||||||
|
url: "https://www.redtube.com".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn get(
|
||||||
|
&self,
|
||||||
|
cache: VideoCache,
|
||||||
|
page: u8,
|
||||||
|
sort: &str,
|
||||||
|
) -> Result<Vec<VideoItem>> {
|
||||||
|
let video_url = format!("{}/mostviewed", self.url);
|
||||||
|
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 proxy = Proxy::all("http://192.168.0.103:8081").unwrap();
|
||||||
|
let client = Client::builder().cert_verification(false).emulation(Emulation::Firefox136).build()?;
|
||||||
|
|
||||||
|
let mut response = client.get(video_url.clone())
|
||||||
|
// .proxy(proxy.clone())
|
||||||
|
.send().await?;
|
||||||
|
if response.status().is_redirection(){
|
||||||
|
|
||||||
|
response = client.get(self.url.clone() + response.headers()["Location"].to_str().unwrap())
|
||||||
|
// .proxy(proxy)
|
||||||
|
.send().await?;
|
||||||
|
}
|
||||||
|
if response.status().is_success() {
|
||||||
|
let text = response.text().await?;
|
||||||
|
let video_items: Vec<VideoItem> = 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)
|
||||||
|
} else {
|
||||||
|
let flare_url = env::var("FLARE_URL").expect("FLARE_URL not set");
|
||||||
|
let flare = Flaresolverr::new(flare_url);
|
||||||
|
let result = flare
|
||||||
|
.solve(FlareSolverrRequest {
|
||||||
|
cmd: "request.get".to_string(),
|
||||||
|
url: video_url.clone(),
|
||||||
|
maxTimeout: 60000,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
let video_items = match result {
|
||||||
|
Ok(res) => {
|
||||||
|
// println!("FlareSolverr response: {}", res);
|
||||||
|
self.get_video_items_from_html(res.solution.response)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error solving FlareSolverr: {}", e);
|
||||||
|
return Err("Failed to solve FlareSolverr".into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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,
|
||||||
|
sort: &str,
|
||||||
|
) -> Result<Vec<VideoItem>> {
|
||||||
|
let search_string = query.to_lowercase().trim().replace(" ", "+");
|
||||||
|
let video_url = format!("{}/?search={}&page={}", self.url, search_string, 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 proxy = Proxy::all("http://192.168.0.103:8081").unwrap();
|
||||||
|
let client = Client::builder().cert_verification(false).emulation(Emulation::Firefox136).build()?;
|
||||||
|
|
||||||
|
let mut response = client.get(video_url.clone())
|
||||||
|
.proxy(proxy.clone())
|
||||||
|
.send().await?;
|
||||||
|
|
||||||
|
if response.status().is_redirection(){
|
||||||
|
|
||||||
|
response = client.get(self.url.clone() + response.headers()["Location"].to_str().unwrap())
|
||||||
|
.proxy(proxy)
|
||||||
|
.send().await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
let text = response.text().await?;
|
||||||
|
let video_items: Vec<VideoItem> = self.get_video_items_from_html_query(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)
|
||||||
|
} else {
|
||||||
|
let flare_url = env::var("FLARE_URL").expect("FLARE_URL not set");
|
||||||
|
let flare = Flaresolverr::new(flare_url);
|
||||||
|
let result = flare
|
||||||
|
.solve(FlareSolverrRequest {
|
||||||
|
cmd: "request.get".to_string(),
|
||||||
|
url: video_url.clone(),
|
||||||
|
maxTimeout: 60000,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
let video_items = match result {
|
||||||
|
Ok(res) => self.get_video_items_from_html_query(res.solution.response),
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error solving FlareSolverr: {}", e);
|
||||||
|
return Err("Failed to solve FlareSolverr".into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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<VideoItem> {
|
||||||
|
if html.is_empty() {
|
||||||
|
println!("HTML is empty");
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
let mut items: Vec<VideoItem> = Vec::new();
|
||||||
|
let video_listing_content = html.split("<script type=\"application/ld+json\">").collect::<Vec<&str>>()[1].split("</script>").collect::<Vec<&str>>()[0];
|
||||||
|
let mut videos: Value = serde_json::from_str(video_listing_content).unwrap();
|
||||||
|
for vid in videos.as_array_mut().unwrap() {
|
||||||
|
let mut video_url: String = vid["embedUrl"].as_str().unwrap_or("").to_string();
|
||||||
|
let mut title: String = vid["name"].as_str().unwrap_or("").to_string();
|
||||||
|
// html decode
|
||||||
|
title = decode(title.as_bytes()).to_string().unwrap_or(title);
|
||||||
|
let id = video_url.split("=").collect::<Vec<&str>>()[1].to_string();
|
||||||
|
let raw_duration = vid["duration"].as_str().unwrap_or("0");
|
||||||
|
let duration = raw_duration.replace("PT", "").replace("S","").parse::<u32>().unwrap();
|
||||||
|
let views: u64 = vid["interactionCount"].as_u64().unwrap_or(0);
|
||||||
|
let thumb = vid["thumbnailUrl"].as_str().unwrap_or("").to_string();
|
||||||
|
|
||||||
|
let mut video_item = VideoItem::new(
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
video_url.to_string(),
|
||||||
|
"redtube".to_string(),
|
||||||
|
thumb,
|
||||||
|
duration,
|
||||||
|
)
|
||||||
|
.views(views as u32)
|
||||||
|
;
|
||||||
|
items.push(video_item);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_video_items_from_html_query(&self, html: String) -> Vec<VideoItem> {
|
||||||
|
if html.is_empty() {
|
||||||
|
println!("HTML is empty");
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
let mut items: Vec<VideoItem> = Vec::new();
|
||||||
|
let video_listing_content = html.split("videos_grid").collect::<Vec<&str>>()[1];
|
||||||
|
let videos = video_listing_content.split("<li id=\"tags_videos_").collect::<Vec<&str>>()[1..].to_vec();
|
||||||
|
for vid in videos {
|
||||||
|
for (i, c) in vid.split("\n").enumerate() {
|
||||||
|
println!("{}: {}", i, c);
|
||||||
|
}
|
||||||
|
let id = vid.split("data-video-id=\"").collect::<Vec<&str>>()[1].split("\"").collect::<Vec<&str>>()[0].to_string();
|
||||||
|
let video_url = format!("{}/{}", self.url, id);
|
||||||
|
let title = vid.split(format!("href=\"/{}\"",id).as_str()).collect::<Vec<&str>>()[1].split(">").collect::<Vec<&str>>()[1].split("<").collect::<Vec<&str>>()[0].trim().to_string();
|
||||||
|
let thumb = vid.split("<img").collect::<Vec<&str>>()[1].split(" src=\"").collect::<Vec<&str>>()[1].split("\"").collect::<Vec<&str>>()[0].to_string();
|
||||||
|
let raw_duration = vid.split("<span class=\"video-properties tm_video_duration\">").collect::<Vec<&str>>()[1].split("</span>").collect::<Vec<&str>>()[0].trim().to_string();
|
||||||
|
let duration = parse_time_to_seconds(&raw_duration).unwrap_or(0) as u32;
|
||||||
|
let views_str = vid.split("<span class='info-views'>").collect::<Vec<&str>>()[1].split("</span>").collect::<Vec<&str>>()[0].trim().to_string();
|
||||||
|
let views = parse_abbreviated_number(&views_str).unwrap_or(0) as u32;
|
||||||
|
|
||||||
|
let video_item = VideoItem::new(
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
video_url,
|
||||||
|
"redtube".to_string(),
|
||||||
|
thumb,
|
||||||
|
duration,
|
||||||
|
)
|
||||||
|
.views(views);
|
||||||
|
items.push(video_item);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Provider for RedtubeProvider {
|
||||||
|
async fn get_videos(
|
||||||
|
&self,
|
||||||
|
cache: VideoCache,
|
||||||
|
pool: DbPool,
|
||||||
|
_channel: String,
|
||||||
|
sort: String,
|
||||||
|
query: Option<String>,
|
||||||
|
page: String,
|
||||||
|
per_page: String,
|
||||||
|
featured: String,
|
||||||
|
category: String,
|
||||||
|
) -> Vec<VideoItem> {
|
||||||
|
let _ = category;
|
||||||
|
let _ = per_page;
|
||||||
|
let _ = featured; // Ignored in this implementation
|
||||||
|
let _ = pool; // Ignored in this implementation
|
||||||
|
let mut sort = sort.to_lowercase();
|
||||||
|
if sort.contains("date"){
|
||||||
|
sort = "mr".to_string();
|
||||||
|
}
|
||||||
|
let videos: std::result::Result<Vec<VideoItem>, Error> = match query {
|
||||||
|
Some(q) => {
|
||||||
|
self.query(cache, page.parse::<u8>().unwrap_or(1), &q, &sort)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.get(cache, page.parse::<u8>().unwrap_or(1), &sort)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match videos {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error fetching videos: {}", e);
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user