porndish fix
This commit is contained in:
@@ -13,9 +13,9 @@ use futures::stream::{self, StreamExt};
|
|||||||
use htmlentity::entity::{ICodedDataTrait, decode};
|
use htmlentity::entity::{ICodedDataTrait, decode};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use scraper::{ElementRef, Html, Selector};
|
use scraper::{ElementRef, Html, Selector};
|
||||||
use std::process::Command;
|
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
error_chain! {
|
error_chain! {
|
||||||
foreign_links {
|
foreign_links {
|
||||||
@@ -263,52 +263,22 @@ impl PorndishProvider {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_with_curl_cffi(url: &str, referer: Option<&str>) -> Result<String> {
|
fn request_headers(referer: Option<&str>) -> Vec<(String, String)> {
|
||||||
let url = url.to_string();
|
let referer = referer
|
||||||
let referer = referer.unwrap_or("").to_string();
|
.filter(|referer| !referer.is_empty())
|
||||||
|
.unwrap_or("https://www.porndish.com/");
|
||||||
|
vec![("Referer".to_string(), referer.to_string())]
|
||||||
|
}
|
||||||
|
|
||||||
let output = tokio::task::spawn_blocking(move || {
|
async fn fetch_html(
|
||||||
Command::new("python3")
|
requester: &mut Requester,
|
||||||
.arg("-c")
|
url: &str,
|
||||||
.arg(
|
referer: Option<&str>,
|
||||||
r#"
|
) -> Result<String> {
|
||||||
import sys
|
requester
|
||||||
from curl_cffi import requests
|
.get_with_headers(url, Self::request_headers(referer), None)
|
||||||
|
|
||||||
url = sys.argv[1]
|
|
||||||
referer = sys.argv[2] if len(sys.argv) > 2 else ""
|
|
||||||
headers = {"Referer": referer} if referer else {}
|
|
||||||
response = requests.get(
|
|
||||||
url,
|
|
||||||
impersonate="chrome",
|
|
||||||
timeout=30,
|
|
||||||
allow_redirects=True,
|
|
||||||
headers=headers,
|
|
||||||
)
|
|
||||||
if response.status_code >= 400:
|
|
||||||
sys.stderr.write(f"status={response.status_code} url={response.url}\n")
|
|
||||||
sys.exit(1)
|
|
||||||
sys.stdout.buffer.write(response.content)
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.arg(url)
|
|
||||||
.arg(referer)
|
|
||||||
.output()
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.map_err(|error| Error::from(format!("spawn_blocking failed: {error}")))?
|
.map_err(|error| Error::from(format!("request failed: {error}")))
|
||||||
.map_err(|error| Error::from(format!("python3 execution failed: {error}")))?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
|
||||||
return Err(Error::from(format!("curl_cffi request failed: {stderr}")));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch_html(url: &str) -> Result<String> {
|
|
||||||
Self::fetch_with_curl_cffi(url, None).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load_filters(
|
async fn load_filters(
|
||||||
@@ -317,6 +287,7 @@ sys.stdout.buffer.write(response.content)
|
|||||||
tags: Arc<RwLock<Vec<FilterOption>>>,
|
tags: Arc<RwLock<Vec<FilterOption>>>,
|
||||||
uploaders: Arc<RwLock<Vec<FilterOption>>>,
|
uploaders: Arc<RwLock<Vec<FilterOption>>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let mut requester = Requester::new();
|
||||||
let link_selector = Self::selector("a[href]")?;
|
let link_selector = Self::selector("a[href]")?;
|
||||||
let article_selector = Self::selector("article.entry-tpl-grid, article.post")?;
|
let article_selector = Self::selector("article.entry-tpl-grid, article.post")?;
|
||||||
let pages = vec![
|
let pages = vec![
|
||||||
@@ -328,7 +299,7 @@ sys.stdout.buffer.write(response.content)
|
|||||||
];
|
];
|
||||||
|
|
||||||
for url in pages {
|
for url in pages {
|
||||||
let html = match Self::fetch_html(&url).await {
|
let html = match Self::fetch_html(&mut requester, &url, None).await {
|
||||||
Ok(html) => html,
|
Ok(html) => html,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
report_provider_error_background(
|
report_provider_error_background(
|
||||||
@@ -648,59 +619,64 @@ sys.stdout.buffer.write(response.content)
|
|||||||
Ok(fragments)
|
Ok(fragments)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn resolve_myvidplay_stream(&self, iframe_url: &str) -> Result<String> {
|
async fn resolve_myvidplay_stream(
|
||||||
let iframe_url = iframe_url.to_string();
|
&self,
|
||||||
let output = tokio::task::spawn_blocking(move || {
|
requester: &mut Requester,
|
||||||
Command::new("python3")
|
iframe_url: &str,
|
||||||
.arg("-c")
|
referer: &str,
|
||||||
.arg(
|
) -> Result<String> {
|
||||||
r#"
|
let html = Self::fetch_html(requester, iframe_url, Some(referer)).await?;
|
||||||
import re
|
let pass_regex = Self::regex(r#"\$\.get\(\s*['"](/pass_md5/[^'"]+)['"]"#)?;
|
||||||
import sys
|
let path = pass_regex
|
||||||
import time
|
.captures(&html)
|
||||||
from curl_cffi import requests
|
.and_then(|captures| captures.get(1).map(|value| value.as_str().to_string()))
|
||||||
|
.ok_or_else(|| Error::from("myvidplay resolution failed: missing pass_md5 path"))?;
|
||||||
|
|
||||||
iframe_url = sys.argv[1]
|
let token = path
|
||||||
session = requests.Session(impersonate="chrome")
|
.trim_end_matches('/')
|
||||||
html = session.get(iframe_url, timeout=30).text
|
.rsplit('/')
|
||||||
match = re.search(r"\$\.get\(\s*['\"](/pass_md5/[^'\"]+)['\"]", html)
|
.next()
|
||||||
if not match:
|
.unwrap_or_default()
|
||||||
sys.stderr.write("missing pass_md5 path\n")
|
.to_string();
|
||||||
sys.exit(1)
|
if token.is_empty() {
|
||||||
path = match.group(1)
|
return Err(Error::from(
|
||||||
token = path.rstrip("/").split("/")[-1]
|
"myvidplay resolution failed: missing pass_md5 token".to_string(),
|
||||||
if not token:
|
));
|
||||||
sys.stderr.write("missing pass_md5 token\n")
|
}
|
||||||
sys.exit(1)
|
|
||||||
if path.startswith("http://") or path.startswith("https://"):
|
|
||||||
pass_url = path
|
|
||||||
else:
|
|
||||||
pass_url = "/".join(iframe_url.split("/")[:3]) + path
|
|
||||||
base = session.get(pass_url, headers={"Referer": iframe_url}, timeout=30).text.strip()
|
|
||||||
if not base or base == "RELOAD" or not base.startswith("http"):
|
|
||||||
sys.stderr.write(f"unusable pass_md5 response: {base[:120]}\n")
|
|
||||||
sys.exit(1)
|
|
||||||
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
|
||||||
now = int(time.time() * 1000)
|
|
||||||
suffix = "".join(chars[(now + i * 17) % len(chars)] for i in range(10))
|
|
||||||
sys.stdout.write(f"{base}{suffix}?token={token}&expiry={now}")
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.arg(iframe_url)
|
|
||||||
.output()
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.map_err(|error| Error::from(format!("spawn_blocking failed: {error}")))?
|
|
||||||
.map_err(|error| Error::from(format!("python3 execution failed: {error}")))?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
let pass_url = if path.starts_with("http://") || path.starts_with("https://") {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
path
|
||||||
|
} else {
|
||||||
|
let base = url::Url::parse(iframe_url)
|
||||||
|
.map_err(|error| Error::from(format!("invalid iframe url: {error}")))?;
|
||||||
|
base.join(&path)
|
||||||
|
.map_err(|error| Error::from(format!("invalid pass_md5 url: {error}")))?
|
||||||
|
.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let base = Self::fetch_html(requester, &pass_url, Some(iframe_url))
|
||||||
|
.await?
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
if base.is_empty() || base == "RELOAD" || !base.starts_with("http") {
|
||||||
return Err(Error::from(format!(
|
return Err(Error::from(format!(
|
||||||
"myvidplay resolution failed: {stderr}"
|
"myvidplay resolution failed: unusable pass_md5 response: {}",
|
||||||
|
&base.chars().take(120).collect::<String>()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let resolved = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
let chars = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
let now = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map_err(|error| Error::from(format!("time error: {error}")))?
|
||||||
|
.as_millis();
|
||||||
|
let suffix = (0..10)
|
||||||
|
.map(|index| {
|
||||||
|
let pos = ((now + (index as u128 * 17)) % chars.len() as u128) as usize;
|
||||||
|
chars[pos] as char
|
||||||
|
})
|
||||||
|
.collect::<String>();
|
||||||
|
let resolved = format!("{base}{suffix}?token={token}&expiry={now}");
|
||||||
if resolved.is_empty() || !resolved.starts_with("http") {
|
if resolved.is_empty() || !resolved.starts_with("http") {
|
||||||
return Err(Error::from(
|
return Err(Error::from(
|
||||||
"myvidplay resolution returned empty url".to_string(),
|
"myvidplay resolution returned empty url".to_string(),
|
||||||
@@ -725,7 +701,7 @@ sys.stdout.write(f"{base}{suffix}?token={token}&expiry={now}")
|
|||||||
html: &str,
|
html: &str,
|
||||||
page_url: &str,
|
page_url: &str,
|
||||||
options: &ServerOptions,
|
options: &ServerOptions,
|
||||||
_requester: &mut Requester,
|
requester: &mut Requester,
|
||||||
) -> Result<VideoItem> {
|
) -> Result<VideoItem> {
|
||||||
let (
|
let (
|
||||||
parsed_title,
|
parsed_title,
|
||||||
@@ -864,7 +840,10 @@ sys.stdout.write(f"{base}{suffix}?token={token}&expiry={now}")
|
|||||||
});
|
});
|
||||||
|
|
||||||
if iframe_url.contains("myvidplay.com") {
|
if iframe_url.contains("myvidplay.com") {
|
||||||
match self.resolve_myvidplay_stream(&iframe_url).await {
|
match self
|
||||||
|
.resolve_myvidplay_stream(requester, &iframe_url, page_url)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(stream_url) => {
|
Ok(stream_url) => {
|
||||||
item.url = stream_url.clone();
|
item.url = stream_url.clone();
|
||||||
let mut format = VideoFormat::new(
|
let mut format = VideoFormat::new(
|
||||||
@@ -919,7 +898,7 @@ sys.stdout.write(f"{base}{suffix}?token={token}&expiry={now}")
|
|||||||
None => Requester::new(),
|
None => Requester::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let html = match Self::fetch_with_curl_cffi(&page_url, None).await {
|
let html = match Self::fetch_html(&mut requester, &page_url, None).await {
|
||||||
Ok(html) => html,
|
Ok(html) => html,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
report_provider_error_background(
|
report_provider_error_background(
|
||||||
@@ -959,10 +938,10 @@ sys.stdout.write(f"{base}{suffix}?token={token}&expiry={now}")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _requester =
|
let mut requester =
|
||||||
crate::providers::requester_or_default(options, "porndish", "missing_requester");
|
crate::providers::requester_or_default(options, "porndish", "missing_requester");
|
||||||
|
|
||||||
let html = match Self::fetch_with_curl_cffi(&url, None).await {
|
let html = match Self::fetch_html(&mut requester, &url, None).await {
|
||||||
Ok(html) => html,
|
Ok(html) => html,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
report_provider_error(
|
report_provider_error(
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
use ntex::http::header::CONTENT_TYPE;
|
use ntex::http::header::{CONTENT_LENGTH, CONTENT_TYPE};
|
||||||
use ntex::{
|
use ntex::{
|
||||||
http::Response,
|
http::Response,
|
||||||
web::{self, HttpRequest, error},
|
web::{self, HttpRequest, error},
|
||||||
};
|
};
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use crate::util::requester::Requester;
|
use crate::util::requester::Requester;
|
||||||
|
|
||||||
pub async fn get_image(
|
pub async fn get_image(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
_requester: web::types::State<Requester>,
|
requester: web::types::State<Requester>,
|
||||||
) -> Result<impl web::Responder, web::Error> {
|
) -> Result<impl web::Responder, web::Error> {
|
||||||
let endpoint = req.match_info().query("endpoint").to_string();
|
let endpoint = req.match_info().query("endpoint").to_string();
|
||||||
let image_url = if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
|
let image_url = if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
|
||||||
@@ -18,45 +17,41 @@ pub async fn get_image(
|
|||||||
format!("https://{}", endpoint.trim_start_matches('/'))
|
format!("https://{}", endpoint.trim_start_matches('/'))
|
||||||
};
|
};
|
||||||
|
|
||||||
let output = tokio::task::spawn_blocking(move || {
|
let upstream = match requester
|
||||||
Command::new("python3")
|
.get_ref()
|
||||||
.arg("-c")
|
.clone()
|
||||||
.arg(
|
.get_raw_with_headers(
|
||||||
r#"
|
image_url.as_str(),
|
||||||
import sys
|
vec![(
|
||||||
from curl_cffi import requests
|
"Referer".to_string(),
|
||||||
|
"https://www.porndish.com/".to_string(),
|
||||||
url = sys.argv[1]
|
)],
|
||||||
response = requests.get(
|
|
||||||
url,
|
|
||||||
impersonate="chrome",
|
|
||||||
timeout=30,
|
|
||||||
allow_redirects=True,
|
|
||||||
headers={"Referer": "https://www.porndish.com/"},
|
|
||||||
)
|
)
|
||||||
if response.status_code >= 400:
|
|
||||||
sys.stderr.write(f"status={response.status_code}\n")
|
|
||||||
sys.exit(1)
|
|
||||||
sys.stderr.write(response.headers.get("content-type", "application/octet-stream"))
|
|
||||||
sys.stdout.buffer.write(response.content)
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.arg(image_url)
|
|
||||||
.output()
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.map_err(error::ErrorBadGateway)?
|
{
|
||||||
.map_err(error::ErrorBadGateway)?;
|
Ok(response) => response,
|
||||||
|
Err(_) => return Ok(web::HttpResponse::NotFound().finish()),
|
||||||
|
};
|
||||||
|
|
||||||
if !output.status.success() {
|
let status = upstream.status();
|
||||||
|
let headers = upstream.headers().clone();
|
||||||
|
let bytes = upstream.bytes().await.map_err(error::ErrorBadGateway)?;
|
||||||
|
|
||||||
|
if !status.is_success() {
|
||||||
return Ok(web::HttpResponse::NotFound().finish());
|
return Ok(web::HttpResponse::NotFound().finish());
|
||||||
}
|
}
|
||||||
|
|
||||||
let content_type = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
let mut resp = Response::build(status);
|
||||||
let mut resp = Response::build(ntex::http::StatusCode::OK);
|
if let Some(ct) = headers.get(CONTENT_TYPE) {
|
||||||
if !content_type.is_empty() {
|
if let Ok(ct_str) = ct.to_str() {
|
||||||
resp.set_header(CONTENT_TYPE, content_type);
|
resp.set_header(CONTENT_TYPE, ct_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(cl) = headers.get(CONTENT_LENGTH) {
|
||||||
|
if let Ok(cl_str) = cl.to_str() {
|
||||||
|
resp.set_header(CONTENT_LENGTH, cl_str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(resp.body(output.stdout))
|
Ok(resp.body(bytes.to_vec()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
/app/target/release/hottub
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd /app
|
||||||
|
exec cargo run --release
|
||||||
|
|||||||
Reference in New Issue
Block a user