shooshtime fix
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
use crate::DbPool;
|
use crate::DbPool;
|
||||||
use crate::api::ClientVersion;
|
use crate::api::ClientVersion;
|
||||||
use crate::providers::{Provider, report_provider_error, report_provider_error_background};
|
use crate::providers::{
|
||||||
|
Provider, build_proxy_url, report_provider_error, report_provider_error_background,
|
||||||
|
strip_url_scheme,
|
||||||
|
};
|
||||||
use crate::status::*;
|
use crate::status::*;
|
||||||
use crate::util::cache::VideoCache;
|
use crate::util::cache::VideoCache;
|
||||||
use crate::util::parse_abbreviated_number;
|
use crate::util::parse_abbreviated_number;
|
||||||
@@ -16,6 +19,7 @@ use regex::Regex;
|
|||||||
use scraper::{ElementRef, Html, Selector};
|
use scraper::{ElementRef, Html, Selector};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::{thread, vec};
|
use std::{thread, vec};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
pub const CHANNEL_METADATA: crate::providers::ProviderChannelMetadata =
|
pub const CHANNEL_METADATA: crate::providers::ProviderChannelMetadata =
|
||||||
crate::providers::ProviderChannelMetadata {
|
crate::providers::ProviderChannelMetadata {
|
||||||
@@ -608,6 +612,41 @@ impl ShooshtimeProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_allowed_detail_url(&self, value: &str) -> bool {
|
||||||
|
let normalized = self.normalize_url(value);
|
||||||
|
let Some(url) = Url::parse(&normalized).ok() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if url.scheme() != "https" {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let Some(host) = url.host_str() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
(host == "shooshtime.com" || host == "www.shooshtime.com")
|
||||||
|
&& url.path().starts_with("/videos/")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proxied_video(
|
||||||
|
&self,
|
||||||
|
options: &ServerOptions,
|
||||||
|
detail_url: &str,
|
||||||
|
quality: Option<&str>,
|
||||||
|
) -> String {
|
||||||
|
if detail_url.is_empty() || !self.is_allowed_detail_url(detail_url) {
|
||||||
|
return detail_url.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut target = strip_url_scheme(detail_url);
|
||||||
|
if let Some(quality) = quality.map(str::trim).filter(|quality| !quality.is_empty()) {
|
||||||
|
target = target.trim_end_matches('/').to_string();
|
||||||
|
target.push_str("/__quality__/");
|
||||||
|
target.push_str(&quality.replace(' ', "%20"));
|
||||||
|
}
|
||||||
|
|
||||||
|
build_proxy_url(options, "shooshtime", &target)
|
||||||
|
}
|
||||||
|
|
||||||
fn search_sort_param(sort: &str) -> Option<&'static str> {
|
fn search_sort_param(sort: &str) -> Option<&'static str> {
|
||||||
match Self::normalize_sort(sort) {
|
match Self::normalize_sort(sort) {
|
||||||
"viewed" => Some("video_viewed"),
|
"viewed" => Some("video_viewed"),
|
||||||
@@ -906,6 +945,7 @@ impl ShooshtimeProvider {
|
|||||||
mut item: VideoItem,
|
mut item: VideoItem,
|
||||||
html: &str,
|
html: &str,
|
||||||
page_url: &str,
|
page_url: &str,
|
||||||
|
options: &ServerOptions,
|
||||||
) -> Result<VideoItem> {
|
) -> Result<VideoItem> {
|
||||||
let flashvars_regex = Self::regex(r#"(?s)var\s+flashvars\s*=\s*\{(.*?)\};"#)?;
|
let flashvars_regex = Self::regex(r#"(?s)var\s+flashvars\s*=\s*\{(.*?)\};"#)?;
|
||||||
let value_regex = |key: &str| Self::regex(&format!(r#"{key}:\s*'([^']*)'"#));
|
let value_regex = |key: &str| Self::regex(&format!(r#"{key}:\s*'([^']*)'"#));
|
||||||
@@ -935,17 +975,17 @@ impl ShooshtimeProvider {
|
|||||||
|
|
||||||
let mut formats = Vec::new();
|
let mut formats = Vec::new();
|
||||||
if let Some(url) = &primary_url {
|
if let Some(url) = &primary_url {
|
||||||
|
let format_url = self.proxied_video(options, page_url, Some(&primary_quality));
|
||||||
formats.push(
|
formats.push(
|
||||||
VideoFormat::new(url.clone(), primary_quality.clone(), "mp4".to_string())
|
VideoFormat::new(format_url, primary_quality.clone(), "mp4".to_string())
|
||||||
.format_id(primary_quality.clone())
|
.format_id(primary_quality.clone()),
|
||||||
.http_header("Referer".to_string(), page_url.to_string()),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let Some(url) = &alt_url {
|
if let Some(url) = &alt_url {
|
||||||
|
let format_url = self.proxied_video(options, page_url, Some(&alt_quality));
|
||||||
formats.push(
|
formats.push(
|
||||||
VideoFormat::new(url.clone(), alt_quality.clone(), "mp4".to_string())
|
VideoFormat::new(format_url, alt_quality.clone(), "mp4".to_string())
|
||||||
.format_id(alt_quality.clone())
|
.format_id(alt_quality.clone()),
|
||||||
.http_header("Referer".to_string(), page_url.to_string()),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1115,6 +1155,10 @@ impl ShooshtimeProvider {
|
|||||||
if let Some(title) = title {
|
if let Some(title) = title {
|
||||||
item.title = title;
|
item.title = title;
|
||||||
}
|
}
|
||||||
|
let proxied_url = self.proxied_video(options, page_url, None);
|
||||||
|
if !proxied_url.is_empty() {
|
||||||
|
item.url = proxied_url;
|
||||||
|
}
|
||||||
if !formats.is_empty() {
|
if !formats.is_empty() {
|
||||||
item = item.formats(formats);
|
item = item.formats(formats);
|
||||||
}
|
}
|
||||||
@@ -1134,7 +1178,7 @@ impl ShooshtimeProvider {
|
|||||||
item = item.uploader_url(uploader_url);
|
item = item.uploader_url(uploader_url);
|
||||||
}
|
}
|
||||||
if !tags.is_empty() {
|
if !tags.is_empty() {
|
||||||
item = item.tags(tags);
|
item.tags = Some(tags);
|
||||||
}
|
}
|
||||||
if item.preview.is_none() {
|
if item.preview.is_none() {
|
||||||
if let Some(preview) = preview_url.as_ref() {
|
if let Some(preview) = preview_url.as_ref() {
|
||||||
@@ -1171,7 +1215,7 @@ impl ShooshtimeProvider {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.apply_detail_video(item, &html, &page_url) {
|
match self.apply_detail_video(item, &html, &page_url, options) {
|
||||||
Ok(item) => item,
|
Ok(item) => item,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
report_provider_error_background(
|
report_provider_error_background(
|
||||||
@@ -1328,7 +1372,26 @@ mod tests {
|
|||||||
"#;
|
"#;
|
||||||
|
|
||||||
let enriched = provider
|
let enriched = provider
|
||||||
.apply_detail_video(item, html, "https://shooshtime.com/videos/example/123/")
|
.apply_detail_video(
|
||||||
|
item,
|
||||||
|
html,
|
||||||
|
"https://shooshtime.com/videos/example/123/",
|
||||||
|
&crate::videos::ServerOptions {
|
||||||
|
featured: None,
|
||||||
|
category: None,
|
||||||
|
sites: None,
|
||||||
|
filter: None,
|
||||||
|
language: None,
|
||||||
|
public_url_base: None,
|
||||||
|
requester: None,
|
||||||
|
network: None,
|
||||||
|
stars: None,
|
||||||
|
categories: None,
|
||||||
|
duration: None,
|
||||||
|
sort: None,
|
||||||
|
sexuality: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(enriched.thumb, "https://shooshtime.com/list-thumb.jpg");
|
assert_eq!(enriched.thumb, "https://shooshtime.com/list-thumb.jpg");
|
||||||
@@ -1337,4 +1400,37 @@ mod tests {
|
|||||||
Some("https://shooshtime.com/detail-thumb.jpg")
|
Some("https://shooshtime.com/detail-thumb.jpg")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn builds_proxied_video_urls() {
|
||||||
|
let provider = ShooshtimeProvider::new();
|
||||||
|
let options = crate::videos::ServerOptions {
|
||||||
|
featured: None,
|
||||||
|
category: None,
|
||||||
|
sites: None,
|
||||||
|
filter: None,
|
||||||
|
language: None,
|
||||||
|
public_url_base: Some("https://example.com".to_string()),
|
||||||
|
requester: None,
|
||||||
|
network: None,
|
||||||
|
stars: None,
|
||||||
|
categories: None,
|
||||||
|
duration: None,
|
||||||
|
sort: None,
|
||||||
|
sexuality: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
provider.proxied_video(&options, "https://shooshtime.com/videos/example/123/", None,),
|
||||||
|
"https://example.com/proxy/shooshtime/shooshtime.com/videos/example/123/"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
provider.proxied_video(
|
||||||
|
&options,
|
||||||
|
"https://shooshtime.com/videos/example/123/",
|
||||||
|
Some("720p"),
|
||||||
|
),
|
||||||
|
"https://example.com/proxy/shooshtime/shooshtime.com/videos/example/123/__quality__/720p"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,10 @@ impl DoodstreamProxy {
|
|||||||
let token_regex = Self::regex(r"\b[0-9a-z]+\b")?;
|
let token_regex = Self::regex(r"\b[0-9a-z]+\b")?;
|
||||||
payload = token_regex
|
payload = token_regex
|
||||||
.replace_all(&payload, |captures: &Captures| {
|
.replace_all(&payload, |captures: &Captures| {
|
||||||
let token = captures.get(0).map(|value| value.as_str()).unwrap_or_default();
|
let token = captures
|
||||||
|
.get(0)
|
||||||
|
.map(|value| value.as_str())
|
||||||
|
.unwrap_or_default();
|
||||||
let Some(index) = Self::decode_base36(token) else {
|
let Some(index) = Self::decode_base36(token) else {
|
||||||
return token.to_string();
|
return token.to_string();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub mod pimpbunnythumb;
|
|||||||
pub mod porndish;
|
pub mod porndish;
|
||||||
pub mod porndishthumb;
|
pub mod porndishthumb;
|
||||||
pub mod pornhd3x;
|
pub mod pornhd3x;
|
||||||
|
pub mod shooshtime;
|
||||||
pub mod spankbang;
|
pub mod spankbang;
|
||||||
pub mod sxyprn;
|
pub mod sxyprn;
|
||||||
|
|
||||||
|
|||||||
301
src/proxies/shooshtime.rs
Normal file
301
src/proxies/shooshtime.rs
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
use ntex::http::Response;
|
||||||
|
use ntex::http::header::{CONTENT_LENGTH, CONTENT_RANGE, CONTENT_TYPE};
|
||||||
|
use ntex::web::{self, HttpRequest, error};
|
||||||
|
use regex::Regex;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::util::requester::Requester;
|
||||||
|
|
||||||
|
const BASE_URL: &str = "https://shooshtime.com";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct SourceCandidate {
|
||||||
|
url: String,
|
||||||
|
quality: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ShooshtimeProxy {}
|
||||||
|
|
||||||
|
impl ShooshtimeProxy {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_detail_request(endpoint: &str) -> Option<(String, Option<String>)> {
|
||||||
|
let endpoint = endpoint.trim().trim_start_matches('/');
|
||||||
|
if endpoint.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (detail_part, quality) = match endpoint.split_once("/__quality__/") {
|
||||||
|
Some((detail, quality)) => {
|
||||||
|
(detail, Some(quality.replace("%20", " ").trim().to_string()))
|
||||||
|
}
|
||||||
|
None => (endpoint, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut detail_url =
|
||||||
|
if detail_part.starts_with("http://") || detail_part.starts_with("https://") {
|
||||||
|
detail_part.to_string()
|
||||||
|
} else {
|
||||||
|
format!("https://{}", detail_part.trim_start_matches('/'))
|
||||||
|
};
|
||||||
|
|
||||||
|
if detail_url.contains("/videos/") && !detail_url.ends_with('/') {
|
||||||
|
detail_url.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::is_allowed_detail_url(&detail_url)
|
||||||
|
.then_some((detail_url, quality.filter(|value| !value.is_empty())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_allowed_detail_url(url: &str) -> bool {
|
||||||
|
let Some(url) = Url::parse(url).ok() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if url.scheme() != "https" {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let Some(host) = url.host_str() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
(host == "shooshtime.com" || host == "www.shooshtime.com")
|
||||||
|
&& url.path().starts_with("/videos/")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_allowed_media_url(url: &str) -> bool {
|
||||||
|
let Some(url) = Url::parse(url).ok() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if url.scheme() != "https" {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let Some(host) = url.host_str() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
(host == "shooshtime.com" || host == "www.shooshtime.com")
|
||||||
|
&& url.path().starts_with("/get_file/")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_url(raw: &str) -> String {
|
||||||
|
let value = raw.trim().replace("\\/", "/");
|
||||||
|
if value.is_empty() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
if value.starts_with("//") {
|
||||||
|
return format!("https:{value}");
|
||||||
|
}
|
||||||
|
if value.starts_with('/') {
|
||||||
|
return format!("{BASE_URL}{value}");
|
||||||
|
}
|
||||||
|
if value.starts_with("http://") {
|
||||||
|
return value.replacen("http://", "https://", 1);
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
fn regex(value: &str) -> Option<Regex> {
|
||||||
|
Regex::new(value).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_js_value(block: &str, regex: &Regex) -> Option<String> {
|
||||||
|
regex
|
||||||
|
.captures(block)
|
||||||
|
.and_then(|value| value.get(1))
|
||||||
|
.map(|value| value.as_str().replace("\\/", "/").replace("\\'", "'"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_sources(html: &str) -> Vec<SourceCandidate> {
|
||||||
|
let Some(flashvars_regex) = Self::regex(r#"(?s)var\s+flashvars\s*=\s*\{(.*?)\};"#) else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
let Some(flashvars) = flashvars_regex
|
||||||
|
.captures(html)
|
||||||
|
.and_then(|value| value.get(1))
|
||||||
|
.map(|value| value.as_str().to_string())
|
||||||
|
else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
|
||||||
|
let value_regex = |key: &str| Self::regex(&format!(r#"{key}:\s*'([^']*)'"#));
|
||||||
|
let primary_url_regex = match value_regex("video_url") {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return vec![],
|
||||||
|
};
|
||||||
|
let primary_quality_regex = match value_regex("video_url_text") {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return vec![],
|
||||||
|
};
|
||||||
|
let alt_url_regex = match value_regex("video_alt_url") {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return vec![],
|
||||||
|
};
|
||||||
|
let alt_quality_regex = match value_regex("video_alt_url_text") {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut sources = Vec::new();
|
||||||
|
|
||||||
|
if let Some(url) = Self::extract_js_value(&flashvars, &primary_url_regex) {
|
||||||
|
let normalized = Self::normalize_url(&url);
|
||||||
|
if !normalized.is_empty() && Self::is_allowed_media_url(&normalized) {
|
||||||
|
sources.push(SourceCandidate {
|
||||||
|
url: normalized,
|
||||||
|
quality: Self::extract_js_value(&flashvars, &primary_quality_regex)
|
||||||
|
.unwrap_or_else(|| "480p".to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(url) = Self::extract_js_value(&flashvars, &alt_url_regex) {
|
||||||
|
let normalized = Self::normalize_url(&url);
|
||||||
|
if !normalized.is_empty() && Self::is_allowed_media_url(&normalized) {
|
||||||
|
sources.push(SourceCandidate {
|
||||||
|
url: normalized,
|
||||||
|
quality: Self::extract_js_value(&flashvars, &alt_quality_regex)
|
||||||
|
.unwrap_or_else(|| "720p".to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sources
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quality_score(label: &str) -> u32 {
|
||||||
|
label
|
||||||
|
.chars()
|
||||||
|
.filter(|value| value.is_ascii_digit())
|
||||||
|
.collect::<String>()
|
||||||
|
.parse::<u32>()
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_source_url(html: &str, quality: Option<&str>) -> Option<String> {
|
||||||
|
let sources = Self::extract_sources(html);
|
||||||
|
if sources.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(quality) = quality {
|
||||||
|
let wanted = quality.trim().to_ascii_lowercase();
|
||||||
|
if let Some(source) = sources
|
||||||
|
.iter()
|
||||||
|
.find(|source| source.quality.trim().to_ascii_lowercase() == wanted)
|
||||||
|
{
|
||||||
|
return Some(source.url.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sources
|
||||||
|
.iter()
|
||||||
|
.max_by_key(|source| Self::quality_score(&source.quality))
|
||||||
|
.map(|source| source.url.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn serve_media(
|
||||||
|
req: HttpRequest,
|
||||||
|
requester: web::types::State<Requester>,
|
||||||
|
) -> Result<impl web::Responder, web::Error> {
|
||||||
|
let endpoint = req.match_info().query("endpoint").to_string();
|
||||||
|
let Some((detail_url, quality)) = ShooshtimeProxy::normalize_detail_request(&endpoint) else {
|
||||||
|
return Ok(web::HttpResponse::BadRequest().finish());
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut requester = requester.get_ref().clone();
|
||||||
|
let html = match requester.get(&detail_url, None).await {
|
||||||
|
Ok(html) => html,
|
||||||
|
Err(_) => return Ok(web::HttpResponse::BadGateway().finish()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(source_url) = ShooshtimeProxy::select_source_url(&html, quality.as_deref()) else {
|
||||||
|
return Ok(web::HttpResponse::BadGateway().finish());
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut headers = vec![("Referer".to_string(), detail_url)];
|
||||||
|
if let Some(range) = req
|
||||||
|
.headers()
|
||||||
|
.get("Range")
|
||||||
|
.and_then(|value| value.to_str().ok())
|
||||||
|
{
|
||||||
|
headers.push(("Range".to_string(), range.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let upstream = match requester.get_raw_with_headers(&source_url, headers).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(_) => return Ok(web::HttpResponse::BadGateway().finish()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let status = upstream.status();
|
||||||
|
let upstream_headers = upstream.headers().clone();
|
||||||
|
let bytes = upstream.bytes().await.map_err(error::ErrorBadGateway)?;
|
||||||
|
|
||||||
|
let mut response = Response::build(status);
|
||||||
|
if let Some(value) = upstream_headers
|
||||||
|
.get(CONTENT_TYPE)
|
||||||
|
.and_then(|value| value.to_str().ok())
|
||||||
|
{
|
||||||
|
response.set_header(CONTENT_TYPE, value);
|
||||||
|
}
|
||||||
|
if let Some(value) = upstream_headers
|
||||||
|
.get(CONTENT_LENGTH)
|
||||||
|
.and_then(|value| value.to_str().ok())
|
||||||
|
{
|
||||||
|
response.set_header(CONTENT_LENGTH, value);
|
||||||
|
}
|
||||||
|
if let Some(value) = upstream_headers
|
||||||
|
.get(CONTENT_RANGE)
|
||||||
|
.and_then(|value| value.to_str().ok())
|
||||||
|
{
|
||||||
|
response.set_header(CONTENT_RANGE, value);
|
||||||
|
}
|
||||||
|
if let Some(value) = upstream_headers
|
||||||
|
.get("Accept-Ranges")
|
||||||
|
.and_then(|value| value.to_str().ok())
|
||||||
|
{
|
||||||
|
response.set_header("Accept-Ranges", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(response.body(bytes.to_vec()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::ShooshtimeProxy;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalizes_detail_endpoint_and_quality() {
|
||||||
|
let (url, quality) = ShooshtimeProxy::normalize_detail_request(
|
||||||
|
"shooshtime.com/videos/example/123/__quality__/720p",
|
||||||
|
)
|
||||||
|
.expect("proxy target should parse");
|
||||||
|
|
||||||
|
assert_eq!(url, "https://shooshtime.com/videos/example/123/");
|
||||||
|
assert_eq!(quality.as_deref(), Some("720p"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn selects_requested_or_best_quality() {
|
||||||
|
let html = r#"
|
||||||
|
<script>
|
||||||
|
var flashvars = {
|
||||||
|
video_url: 'https://shooshtime.com/get_file/1/token/1/2/3.mp4/?x=1',
|
||||||
|
video_url_text: '480p',
|
||||||
|
video_alt_url: 'https://shooshtime.com/get_file/1/token/1/2/3_720p.mp4/?x=2',
|
||||||
|
video_alt_url_text: '720p'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
ShooshtimeProxy::select_source_url(html, Some("480p")).as_deref(),
|
||||||
|
Some("https://shooshtime.com/get_file/1/token/1/2/3.mp4/?x=1")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ShooshtimeProxy::select_source_url(html, None).as_deref(),
|
||||||
|
Some("https://shooshtime.com/get_file/1/token/1/2/3_720p.mp4/?x=2")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,11 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||||||
.route(web::post().to(proxy2redirect))
|
.route(web::post().to(proxy2redirect))
|
||||||
.route(web::get().to(proxy2redirect)),
|
.route(web::get().to(proxy2redirect)),
|
||||||
)
|
)
|
||||||
|
.service(
|
||||||
|
web::resource("/shooshtime/{endpoint}*")
|
||||||
|
.route(web::post().to(crate::proxies::shooshtime::serve_media))
|
||||||
|
.route(web::get().to(crate::proxies::shooshtime::serve_media)),
|
||||||
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/pimpbunny/{endpoint}*")
|
web::resource("/pimpbunny/{endpoint}*")
|
||||||
.route(web::post().to(proxy2redirect))
|
.route(web::post().to(proxy2redirect))
|
||||||
|
|||||||
Reference in New Issue
Block a user