fixes and cleanup
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use crate::DbPool;
|
||||
use crate::api::ClientVersion;
|
||||
use crate::providers::Provider;
|
||||
use crate::providers::{Provider, report_provider_error_background, requester_or_default};
|
||||
use crate::status::*;
|
||||
use crate::util::cache::VideoCache;
|
||||
use crate::util::discord::send_discord_error_report;
|
||||
@@ -8,10 +8,11 @@ use crate::util::time::parse_time_to_seconds;
|
||||
use crate::videos::{ServerOptions, VideoFormat, VideoItem};
|
||||
use async_trait::async_trait;
|
||||
use error_chain::error_chain;
|
||||
use htmlentity::entity::{decode, ICodedDataTrait};
|
||||
use htmlentity::entity::{ICodedDataTrait, decode};
|
||||
use std::fmt::Write;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::vec;
|
||||
use std::fmt::Write;
|
||||
use url::form_urlencoded::byte_serialize;
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
@@ -135,9 +136,7 @@ impl PmvhavenProvider {
|
||||
fn is_direct_media_url(url: &str) -> bool {
|
||||
let lower = url.to_ascii_lowercase();
|
||||
(lower.starts_with("http://") || lower.starts_with("https://"))
|
||||
&& (lower.contains("/videos/")
|
||||
|| lower.contains(".mp4")
|
||||
|| lower.contains(".m3u8"))
|
||||
&& (lower.contains("/videos/") || lower.contains(".mp4") || lower.contains(".m3u8"))
|
||||
}
|
||||
|
||||
fn pick_downloadable_media_url(&self, video: &serde_json::Value) -> Option<String> {
|
||||
@@ -192,67 +191,119 @@ impl PmvhavenProvider {
|
||||
_ => "",
|
||||
};
|
||||
|
||||
let endpoint = if search.is_empty() {
|
||||
"api/videos"
|
||||
} else {
|
||||
"api/videos/search"
|
||||
};
|
||||
|
||||
let mut url = format!(
|
||||
"{}/{endpoint}?limit=100&page={page}{duration}{sort}",
|
||||
self.url
|
||||
);
|
||||
let encoded_search: String = byte_serialize(search.as_bytes()).collect();
|
||||
|
||||
let mut extra_filters = String::new();
|
||||
if let Ok(stars) = self.stars.read() {
|
||||
if let Some(star) = stars.iter().find(|s| s.eq_ignore_ascii_case(&search)) {
|
||||
url.push_str(&format!("&stars={star}"));
|
||||
let encoded_star: String = byte_serialize(star.as_bytes()).collect();
|
||||
extra_filters.push_str(&format!("&stars={encoded_star}"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(cats) = self.categories.read() {
|
||||
if let Some(cat) = cats.iter().find(|c| c.eq_ignore_ascii_case(&search)) {
|
||||
url.push_str(&format!("&tagMode=OR&tags={cat}&expandTags=false"));
|
||||
let encoded_cat: String = byte_serialize(cat.as_bytes()).collect();
|
||||
extra_filters.push_str(&format!("&tagMode=OR&tags={encoded_cat}&expandTags=false"));
|
||||
}
|
||||
}
|
||||
|
||||
if !search.is_empty() {
|
||||
url.push_str(&format!("&q={search}"));
|
||||
let mut urls = vec![];
|
||||
if search.is_empty() {
|
||||
urls.push(format!(
|
||||
"{}/api/videos?limit=100&page={page}{duration}{sort}{extra_filters}",
|
||||
self.url
|
||||
));
|
||||
} else {
|
||||
urls.push(format!(
|
||||
"{}/api/videos/search?limit=100&page={page}{duration}{sort}{extra_filters}&q={encoded_search}",
|
||||
self.url
|
||||
));
|
||||
urls.push(format!(
|
||||
"{}/api/videos/search?limit=100&page={page}{duration}{sort}{extra_filters}&query={encoded_search}",
|
||||
self.url
|
||||
));
|
||||
urls.push(format!(
|
||||
"{}/api/videos?limit=100&page={page}{duration}{sort}{extra_filters}&q={encoded_search}",
|
||||
self.url
|
||||
));
|
||||
urls.push(format!(
|
||||
"{}/api/videos?limit=100&page={page}{duration}{sort}{extra_filters}&search={encoded_search}",
|
||||
self.url
|
||||
));
|
||||
}
|
||||
if let Some((time, items)) = cache.get(&url) {
|
||||
if time.elapsed().unwrap_or_default().as_secs() < 300 {
|
||||
return Ok(items.clone());
|
||||
|
||||
let mut requester = requester_or_default(&options, "pmvhaven", "query");
|
||||
for url in urls {
|
||||
if let Some((time, items)) = cache.get(&url) {
|
||||
if time.elapsed().unwrap_or_default().as_secs() < 300 {
|
||||
return Ok(items.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let text = match requester.get(&url, None).await {
|
||||
Ok(text) => text,
|
||||
Err(err) => {
|
||||
report_provider_error_background(
|
||||
"pmvhaven",
|
||||
"get.request",
|
||||
&format!("url={url}; error={err}"),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let json: serde_json::Value = match serde_json::from_str(&text) {
|
||||
Ok(json) => json,
|
||||
Err(err) => {
|
||||
report_provider_error_background(
|
||||
"pmvhaven",
|
||||
"parse.json",
|
||||
&format!("url={url}; error={err}"),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let items = self.get_video_items_from_json(json).await;
|
||||
|
||||
if !items.is_empty() {
|
||||
cache.remove(&url);
|
||||
cache.insert(url, items.clone());
|
||||
return Ok(items);
|
||||
}
|
||||
}
|
||||
|
||||
let mut requester = match options.requester {
|
||||
Some(r) => r,
|
||||
None => return Ok(vec![]),
|
||||
};
|
||||
|
||||
let text = requester.get(&url, None).await.unwrap_or_default();
|
||||
let json = serde_json::from_str(&text).unwrap_or(serde_json::Value::Null);
|
||||
let items = self.get_video_items_from_json(json).await;
|
||||
|
||||
if !items.is_empty() {
|
||||
cache.remove(&url);
|
||||
cache.insert(url, items.clone());
|
||||
}
|
||||
Ok(items)
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
async fn get_video_items_from_json(&self, json: serde_json::Value) -> Vec<VideoItem> {
|
||||
let mut items = vec![];
|
||||
|
||||
if !json.get("success").and_then(|v| v.as_bool()).unwrap_or(false) {
|
||||
if !json
|
||||
.get("success")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return items;
|
||||
}
|
||||
|
||||
let videos = json.get("data").and_then(|v| v.as_array()).cloned().unwrap_or_default();
|
||||
let videos = json
|
||||
.get("data")
|
||||
.and_then(|v| v.as_array())
|
||||
.or_else(|| json.get("videos").and_then(|v| v.as_array()))
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
for video in videos {
|
||||
let title = decode(video.get("title").and_then(|v| v.as_str()).unwrap_or("").as_bytes())
|
||||
.to_string()
|
||||
.unwrap_or_default();
|
||||
let title = decode(
|
||||
video
|
||||
.get("title")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.as_bytes(),
|
||||
)
|
||||
.to_string()
|
||||
.unwrap_or_default();
|
||||
|
||||
let id = video
|
||||
.get("_id")
|
||||
@@ -266,14 +317,36 @@ impl PmvhavenProvider {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let thumb = video.get("thumbnailUrl").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let preview = video.get("previewUrl").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let thumb = video
|
||||
.get("thumbnailUrl")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let preview = video
|
||||
.get("previewUrl")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
let views = video.get("views").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||
let duration = parse_time_to_seconds(video.get("duration").and_then(|v| v.as_str()).unwrap_or("0")).unwrap_or(0);
|
||||
let duration = parse_time_to_seconds(
|
||||
video
|
||||
.get("duration")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("0"),
|
||||
)
|
||||
.unwrap_or(0);
|
||||
|
||||
let tags = video.get("tags").and_then(|v| v.as_array()).cloned().unwrap_or_default();
|
||||
let stars = video.get("starsTags").and_then(|v| v.as_array()).cloned().unwrap_or_default();
|
||||
let tags = video
|
||||
.get("tags")
|
||||
.and_then(|v| v.as_array())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let stars = video
|
||||
.get("starsTags")
|
||||
.and_then(|v| v.as_array())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
for t in tags.iter() {
|
||||
if let Some(s) = t.as_str() {
|
||||
let decoded = decode(s.as_bytes()).to_string().unwrap_or_default();
|
||||
@@ -293,14 +366,21 @@ impl PmvhavenProvider {
|
||||
"mp4".to_string()
|
||||
};
|
||||
items.push(
|
||||
VideoItem::new(id, title, video_url.clone(), "pmvhaven".into(), thumb, duration as u32)
|
||||
.views(views as u32)
|
||||
.formats(vec![VideoFormat::new(
|
||||
video_url,
|
||||
"1080".to_string(),
|
||||
format_type,
|
||||
)])
|
||||
.preview(preview)
|
||||
VideoItem::new(
|
||||
id,
|
||||
title,
|
||||
video_url.clone(),
|
||||
"pmvhaven".into(),
|
||||
thumb,
|
||||
duration as u32,
|
||||
)
|
||||
.views(views as u32)
|
||||
.formats(vec![VideoFormat::new(
|
||||
video_url,
|
||||
"1080".to_string(),
|
||||
format_type,
|
||||
)])
|
||||
.preview(preview),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -308,6 +388,56 @@ impl PmvhavenProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PmvhavenProvider;
|
||||
use serde_json::json;
|
||||
|
||||
#[tokio::test]
|
||||
async fn parses_videos_from_videos_key() {
|
||||
let provider = PmvhavenProvider::new();
|
||||
let payload = json!({
|
||||
"success": true,
|
||||
"videos": [{
|
||||
"_id": "abc123",
|
||||
"title": "Sample Title",
|
||||
"videoUrl": "https://video.pmvhaven.com/videos/sample.mp4",
|
||||
"thumbnailUrl": "https://video.pmvhaven.com/thumbnails/sample.webp",
|
||||
"previewUrl": "https://video.pmvhaven.com/previews/sample.mp4",
|
||||
"views": 42,
|
||||
"duration": "2:11",
|
||||
"tags": [],
|
||||
"starsTags": []
|
||||
}]
|
||||
});
|
||||
|
||||
let items = provider.get_video_items_from_json(payload).await;
|
||||
assert_eq!(items.len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn parses_videos_from_data_key() {
|
||||
let provider = PmvhavenProvider::new();
|
||||
let payload = json!({
|
||||
"success": true,
|
||||
"data": [{
|
||||
"_id": "abc123",
|
||||
"title": "Sample Title",
|
||||
"videoUrl": "https://video.pmvhaven.com/videos/sample.mp4",
|
||||
"thumbnailUrl": "https://video.pmvhaven.com/thumbnails/sample.webp",
|
||||
"previewUrl": "https://video.pmvhaven.com/previews/sample.mp4",
|
||||
"views": 42,
|
||||
"duration": "2:11",
|
||||
"tags": [],
|
||||
"starsTags": []
|
||||
}]
|
||||
});
|
||||
|
||||
let items = provider.get_video_items_from_json(payload).await;
|
||||
assert_eq!(items.len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Provider for PmvhavenProvider {
|
||||
async fn get_videos(
|
||||
@@ -332,14 +462,15 @@ impl Provider for PmvhavenProvider {
|
||||
let _ = writeln!(chain_str, "{}. {}", i + 1, cause);
|
||||
}
|
||||
send_discord_error_report(
|
||||
e.to_string(),
|
||||
Some(chain_str),
|
||||
Some("PMVHaven Provider"),
|
||||
Some("Failed to load videos from PMVHaven"),
|
||||
file!(),
|
||||
line!(),
|
||||
module_path!(),
|
||||
).await;
|
||||
e.to_string(),
|
||||
Some(chain_str),
|
||||
Some("PMVHaven Provider"),
|
||||
Some("Failed to load videos from PMVHaven"),
|
||||
file!(),
|
||||
line!(),
|
||||
module_path!(),
|
||||
)
|
||||
.await;
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user