From 8b59a8ef157372d9412f0562200f19883bcac66a Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Mon, 5 Feb 2024 17:43:12 +0200 Subject: [PATCH 1/5] feat: Add Stream::RelativeUrl Signed-off-by: Lachezar Lechev --- src/types/resource/stream.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/types/resource/stream.rs b/src/types/resource/stream.rs index da05478b9..22d5210a0 100644 --- a/src/types/resource/stream.rs +++ b/src/types/resource/stream.rs @@ -116,6 +116,10 @@ impl Stream { self.magnet_url().map(|magnet_url| magnet_url.to_string()) } StreamSource::Url { url } => Some(url.to_string()), + StreamSource::RelativeUrl { relative_url: _ } => { + // we cannot return a downloadable link for a third-party, dynamic addon + None + } StreamSource::Torrent { .. } => { self.magnet_url().map(|magnet_url| magnet_url.to_string()) } @@ -248,6 +252,26 @@ pub enum StreamSource { Url { url: Url, }, + /// A relative url (url encoded) which will be appended to the addon transport url. + /// + /// # Examples + /// + /// + /// + /// ``` + /// use url::Url; + /// + /// let watchhub_transport_url: Url = "https://watchhub.strem.io/manifest.json".parse().unwrap(); + /// + /// let relative_url = "/stream/movie/tt21860836.json"; + /// + /// let addon_call_url: Url = "https://watchhub.strem.io/stream/movie/tt21860836.json".parse().unwrap(); + /// assert_eq!(watchhub_transport_url.join(relative_url), addon_call_url); + /// ``` + #[serde(rename_all = "camelCase")] + RelativeUrl { + relative_url: String, + }, #[cfg_attr(test, derivative(Default))] #[serde(rename_all = "camelCase")] YouTube { From ca43aa50017a22539efaf35b1cbd2984d5e6cc84 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Mon, 12 Feb 2024 18:33:16 +0200 Subject: [PATCH 2/5] feat: addons relative url substitution for Stream and Subtitle Signed-off-by: Lachezar Lechev --- .../http_transport/http_transport.rs | 17 +- src/models/meta_details.rs | 4 +- src/types/addon/response.rs | 219 ++++++++++++++++++ src/types/resource/stream.rs | 103 +++++--- src/types/resource/subtitles.rs | 7 +- .../ctx/notifications/update_notifications.rs | 6 +- .../deep_links/external_player_link.rs | 6 +- .../deep_links/stream_deep_links.rs | 12 +- src/unit_tests/serde/stream_source.rs | 6 +- src/unit_tests/serde/subtitles.rs | 4 +- 10 files changed, 329 insertions(+), 55 deletions(-) diff --git a/src/addon_transport/http_transport/http_transport.rs b/src/addon_transport/http_transport/http_transport.rs index 80e54e113..3e79d3990 100644 --- a/src/addon_transport/http_transport/http_transport.rs +++ b/src/addon_transport/http_transport/http_transport.rs @@ -4,9 +4,10 @@ use crate::constants::{ADDON_LEGACY_PATH, ADDON_MANIFEST_PATH, URI_COMPONENT_ENC use crate::runtime::{Env, EnvError, EnvFutureExt, TryEnvFuture}; use crate::types::addon::{Manifest, ResourcePath, ResourceResponse}; use crate::types::query_params_encode; -use futures::future; +use futures::{future, FutureExt, TryFutureExt}; use http::Request; use percent_encoding::utf8_percent_encode; +use std::future::IntoFuture; use std::marker::PhantomData; use url::Url; @@ -61,7 +62,19 @@ impl AddonTransport for AddonHTTPTransport { .as_str() .replace(ADDON_MANIFEST_PATH, &path); let request = Request::get(&url).body(()).expect("request builder failed"); - E::fetch(request) + let addon_transport_url = self.transport_url.clone(); + E::fetch::<_, ResourceResponse>(request) + .map(move |result| match result { + Ok(mut response_result) => { + // convert all relative paths in StreamSource::Url and `Subtitle.url` + // with absolute + response_result.convert_relative_paths(addon_transport_url.clone()); + + Ok(response_result) + } + Err(err) => Err(err), + }) + .boxed_env() } fn manifest(&self) -> TryEnvFuture { if self.transport_url.path().ends_with(ADDON_LEGACY_PATH) { diff --git a/src/models/meta_details.rs b/src/models/meta_details.rs index 202c6e0c3..6defb03fd 100644 --- a/src/models/meta_details.rs +++ b/src/models/meta_details.rs @@ -80,8 +80,8 @@ impl UpdateWithCtx for MetaDetails { ); let watched_effects = watched_update(&mut self.watched, &self.meta_items, &self.library_item); - let libraty_item_sync_effects = library_item_sync(&self.library_item, &ctx.profile); - libraty_item_sync_effects + let library_item_sync_effects = library_item_sync(&self.library_item, &ctx.profile); + library_item_sync_effects .join(selected_effects) .join(selected_override_effects) .join(meta_items_effects) diff --git a/src/types/addon/response.rs b/src/types/addon/response.rs index f764d6736..4fd07b41d 100644 --- a/src/types/addon/response.rs +++ b/src/types/addon/response.rs @@ -1,6 +1,8 @@ use derive_more::TryInto; +use itertools::Itertools; use serde::{de::Deserializer, Deserialize, Serialize}; use serde_with::{serde_as, VecSkipError}; +use url::Url; use crate::types::{ addon::DescriptorPreview, @@ -36,6 +38,97 @@ pub enum ResourceResponse { }, } +impl ResourceResponse { + /// Convert any relative path in `Stream.source` with absolute url using the provided addon's transport url + pub fn convert_relative_paths(&mut self, addon_transport_url: Url) { + match self { + ResourceResponse::Metas { ref mut metas } => { + metas + .iter_mut() + .map(|meta_item_preview| { + meta_item_preview + .trailer_streams + .iter_mut() + .filter_map(|stream| stream.with_addon_url(&addon_transport_url).ok()) + // .collect::>() + }) + .flatten() + .collect() + } + ResourceResponse::MetasDetailed { + ref mut metas_detailed, + } => { + metas_detailed + .iter_mut() + .map(|meta_item| { + // MetaItem videos + meta_item + .videos + .iter_mut() + .map(|video| { + // MetaItem video streams + video + .streams + .iter_mut() + .filter_map(|stream| { + stream.with_addon_url(&addon_transport_url).ok() + }) + .chain( + // MetaItem videos' trailer streams + video.trailer_streams.iter_mut().filter_map(|stream| { + stream.with_addon_url(&addon_transport_url).ok() + }), + ) + }) + .flatten() + // Trailer Streams of the MetaItemPreview + .chain(meta_item.preview.trailer_streams.iter_mut().filter_map( + |stream| stream.with_addon_url(&addon_transport_url).ok(), + )) + }) + .flatten() + .collect() + } + ResourceResponse::Meta { meta } => meta + .videos + .iter_mut() + .map(|video| { + // MetaItem video streams + video + .streams + .iter_mut() + .filter_map(|stream| stream.with_addon_url(&addon_transport_url).ok()) + .chain( + // MetaItem videos' trailer streams + video.trailer_streams.iter_mut().filter_map(|stream| { + stream.with_addon_url(&addon_transport_url).ok() + }), + ) + }) + .flatten() + // Trailer Streams of the MetaItemPreview + .chain( + meta.preview + .trailer_streams + .iter_mut() + .filter_map(|stream| stream.with_addon_url(&addon_transport_url).ok()), + ) + .collect(), + ResourceResponse::Streams { streams } => streams + .iter_mut() + .filter_map(|stream| stream.with_addon_url(&addon_transport_url).ok()) + .collect(), + ResourceResponse::Subtitles { subtitles } => subtitles + .iter_mut() + .filter_map(|subtitle| subtitle.url.with_addon_url(&addon_transport_url).ok()) + .collect(), + ResourceResponse::Addons { addons } => { + // for addons - do nothing + }, + } + } +} + #[serde_as] #[derive(Clone, Serialize, Deserialize, Debug)] #[serde(transparent)] @@ -96,3 +189,129 @@ impl<'de> Deserialize<'de> for ResourceResponse { } } } + +#[cfg(test)] +mod tests { + use once_cell::sync::Lazy; + use url::Url; + + use crate::types::resource::{ + MetaItem, MetaItemPreview, Stream, StreamBehaviorHints, StreamSource, UrlExtended, Video, + }; + + use super::ResourceResponse; + + pub static ADDON_TRANSPORT_URL: Lazy = + Lazy::new(|| "https://example-addon.com/manifest.json".parse().unwrap()); + + #[test] + fn replace_relative_path_for_meta() { + let relative_stream = Stream { + source: StreamSource::Url { + url: UrlExtended::RelativePath("/stream/path/tt123456.json".into()), + }, + name: None, + description: None, + thumbnail: None, + subtitles: vec![], + behavior_hints: StreamBehaviorHints::default(), + }; + + let relative_video_trailer_stream = Stream { + source: StreamSource::Url { + url: UrlExtended::RelativePath("/stream/video/trailer/path/tt123456:1.json".into()), + }, + name: None, + description: None, + thumbnail: None, + subtitles: vec![], + behavior_hints: StreamBehaviorHints::default(), + }; + + let relative_trailer_stream = Stream { + source: StreamSource::Url { + url: UrlExtended::RelativePath("/stream/trailer/path/tt123456.json".into()), + }, + name: None, + description: None, + thumbnail: None, + subtitles: vec![], + behavior_hints: StreamBehaviorHints::default(), + }; + + let relative_meta_preview = { + let mut preview = MetaItemPreview::default(); + preview + .trailer_streams + .push(relative_trailer_stream.clone()); + preview + }; + + let relative_video_stream = { + let mut video = Video::default(); + video + .trailer_streams + .push(relative_video_trailer_stream.clone()); + + video.streams.push(relative_stream.clone()); + video + }; + + // Meta response with relative path + { + let mut resource_response = ResourceResponse::Meta { + meta: MetaItem { + preview: relative_meta_preview.clone(), + videos: vec![relative_video_stream.clone()], + }, + }; + + resource_response.convert_relative_paths(ADDON_TRANSPORT_URL.clone()); + + let meta = match resource_response { + ResourceResponse::Meta { meta } => meta, + _ => unreachable!(), + }; + // Meta trailer stream + assert_eq!( + "https://example-addon.com/stream/trailer/path/tt123456.json", + &meta + .preview + .trailer_streams + .first() + .unwrap() + .download_url() + .unwrap() + ); + + // Video stream + assert_eq!( + "https://example-addon.com/stream/path/tt123456.json", + &meta + .videos + .first() + .unwrap() + .streams + .first() + .unwrap() + .download_url() + .unwrap() + ); + + // Video trailer stream + + assert_eq!( + "https://example-addon.com/stream/video/trailer/path/tt123456:1.json", + &meta + .videos + .first() + .unwrap() + .trailer_streams + .first() + .unwrap() + .download_url() + .unwrap() + ); + } + } +} diff --git a/src/types/resource/stream.rs b/src/types/resource/stream.rs index 22d5210a0..201b2fc8b 100644 --- a/src/types/resource/stream.rs +++ b/src/types/resource/stream.rs @@ -13,8 +13,9 @@ use serde::{Deserialize, Deserializer, Serialize}; use serde_with::{serde_as, DefaultOnNull}; use std::collections::HashMap; use std::io::Write; +use std::str::FromStr; use stremio_serde_hex::{SerHex, Strict}; -use url::{form_urlencoded, Url}; +use url::{form_urlencoded, ParseError, Url}; #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -33,10 +34,64 @@ pub struct Stream { pub behavior_hints: StreamBehaviorHints, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged, from = "String")] +pub enum UrlExtended { + Url(Url), + RelativePath(String), +} + +impl From for UrlExtended { + fn from(value: String) -> Self { + value.parse().unwrap() + } +} +impl UrlExtended { + /// This method will replace the relative path with absolute one using the provided addon transport URL, + /// only if the we have a [`UrlExtended::RelativePath`]. + /// + /// Otherwise, it leaves the [`UrlExtended`] unchanged. + pub fn with_addon_url(&mut self, addon_transport_url: &Url) -> Result<(), ParseError> { + match &self { + UrlExtended::RelativePath(path) => { + *self = UrlExtended::Url(addon_transport_url.join(&path)?); + } + _ => {} + }; + + Ok(()) + } +} + +impl FromStr for UrlExtended { + type Err = (); + + fn from_str(s: &str) -> Result { + match s.parse::() { + Ok(url) => Ok(Self::Url(url)), + Err(_err) => Ok(Self::RelativePath(s.into())), + } + } +} + impl Stream { + /// This method will replace the relative path with absolute one using the provided addon transport URL, + /// only if the stream source is [`StreamSource::Url`] and contains a [`UrlExtended::RelativePath`]. + /// + /// Otherwise, it leaves the [`Stream`] unchanged. + pub fn with_addon_url(&mut self, addon_transport_url: &Url) -> Result<(), ParseError> { + if let StreamSource::Url { url } = &mut self.source { + url.with_addon_url(addon_transport_url)?; + } + + Ok(()) + } + pub fn magnet_url(&self) -> Option { match &self.source { - StreamSource::Url { url } if url.scheme() == "magnet" => Magnet::new(url.as_str()).ok(), + StreamSource::Url { + url: UrlExtended::Url(url), + } if url.scheme() == "magnet" => Magnet::new(url.as_str()).ok(), StreamSource::Torrent { info_hash, announce, @@ -112,14 +167,13 @@ impl Stream { } pub fn download_url(&self) -> Option { match &self.source { - StreamSource::Url { url } if url.scheme() == "magnet" => { - self.magnet_url().map(|magnet_url| magnet_url.to_string()) - } - StreamSource::Url { url } => Some(url.to_string()), - StreamSource::RelativeUrl { relative_url: _ } => { - // we cannot return a downloadable link for a third-party, dynamic addon - None - } + StreamSource::Url { url } => match url { + UrlExtended::Url(url) if url.scheme() == "magnet" => { + self.magnet_url().map(|magnet_url| magnet_url.to_string()) + } + UrlExtended::Url(url) => Some(url.to_string()), + UrlExtended::RelativePath(_) => None, + }, StreamSource::Torrent { .. } => { self.magnet_url().map(|magnet_url| magnet_url.to_string()) } @@ -140,7 +194,12 @@ impl Stream { } pub fn streaming_url(&self, streaming_server_url: Option<&Url>) -> Option { match (&self.source, streaming_server_url) { - (StreamSource::Url { url }, streaming_server_url) if url.scheme() != "magnet" => { + ( + StreamSource::Url { + url: UrlExtended::Url(url), + }, + streaming_server_url, + ) if url.scheme() != "magnet" => { // If proxy headers are set and streaming server is available, build the proxied streaming url from streaming server url // Otherwise return the url match (&self.behavior_hints.proxy_headers, streaming_server_url) { @@ -250,27 +309,7 @@ impl Stream { #[serde(untagged)] pub enum StreamSource { Url { - url: Url, - }, - /// A relative url (url encoded) which will be appended to the addon transport url. - /// - /// # Examples - /// - /// - /// - /// ``` - /// use url::Url; - /// - /// let watchhub_transport_url: Url = "https://watchhub.strem.io/manifest.json".parse().unwrap(); - /// - /// let relative_url = "/stream/movie/tt21860836.json"; - /// - /// let addon_call_url: Url = "https://watchhub.strem.io/stream/movie/tt21860836.json".parse().unwrap(); - /// assert_eq!(watchhub_transport_url.join(relative_url), addon_call_url); - /// ``` - #[serde(rename_all = "camelCase")] - RelativeUrl { - relative_url: String, + url: UrlExtended, }, #[cfg_attr(test, derivative(Default))] #[serde(rename_all = "camelCase")] diff --git a/src/types/resource/subtitles.rs b/src/types/resource/subtitles.rs index 150ce8380..0ca9fdcd1 100644 --- a/src/types/resource/subtitles.rs +++ b/src/types/resource/subtitles.rs @@ -1,7 +1,8 @@ #[cfg(test)] use derivative::Derivative; use serde::{Deserialize, Serialize}; -use url::Url; + +use super::UrlExtended; #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[cfg_attr(test, derive(Derivative))] @@ -10,7 +11,7 @@ pub struct Subtitles { pub lang: String, #[cfg_attr( test, - derivative(Default(value = "Url::parse(\"protocol://host\").unwrap()")) + derivative(Default(value = "UrlExtended::Url(url::Url::parse(\"protocol://host\").unwrap())")) )] - pub url: Url, + pub url: UrlExtended, } diff --git a/src/unit_tests/ctx/notifications/update_notifications.rs b/src/unit_tests/ctx/notifications/update_notifications.rs index 03923bdd0..87c9e0b4f 100644 --- a/src/unit_tests/ctx/notifications/update_notifications.rs +++ b/src/unit_tests/ctx/notifications/update_notifications.rs @@ -36,7 +36,7 @@ use crate::{ profile::Profile, resource::{ MetaItem, MetaItemId, MetaItemPreview, PosterShape, SeriesInfo, Stream, StreamSource, - Video, VideoId, + UrlExtended, Video, VideoId, }, search_history::SearchHistoryBucket, streams::StreamsBucket, @@ -90,7 +90,7 @@ fn test_pull_notifications_and_play_in_player() { addon_catalogs: vec![], behavior_hints: Default::default(), }, - transport_url: Url::parse("https://addon_1.com/manifest.json").unwrap(), + transport_url: "https://addon_1.com/manifest.json".parse().unwrap(), flags: Default::default(), }); @@ -294,7 +294,7 @@ fn test_pull_notifications_and_play_in_player() { action: Action::Load(ActionLoad::Player(Box::new(PlayerSelected { stream: Stream { source: StreamSource::Url { - url: Url::parse("https://example.com/stream.mp4").unwrap(), + url: UrlExtended::Url("https://example.com/stream.mp4".parse().unwrap()), }, name: None, description: None, diff --git a/src/unit_tests/deep_links/external_player_link.rs b/src/unit_tests/deep_links/external_player_link.rs index 27a89ef60..b5701633e 100644 --- a/src/unit_tests/deep_links/external_player_link.rs +++ b/src/unit_tests/deep_links/external_player_link.rs @@ -1,7 +1,7 @@ use crate::constants::{BASE64, URI_COMPONENT_ENCODE_SET}; use crate::deep_links::ExternalPlayerLink; use crate::types::profile::Settings; -use crate::types::resource::{Stream, StreamSource}; +use crate::types::resource::{Stream, StreamSource, UrlExtended}; use base64::Engine; use percent_encoding::utf8_percent_encode; use std::convert::TryFrom; @@ -17,7 +17,7 @@ const STREAMING_SERVER_URL: &str = "http://127.0.0.1:11470"; fn external_player_link_magnet() { let stream = Stream { source: StreamSource::Url { - url: Url::from_str(MAGNET_STR_URL).unwrap(), + url: MAGNET_STR_URL.parse().unwrap(), }, name: None, description: None, @@ -36,7 +36,7 @@ fn external_player_link_magnet() { fn external_player_link_http() { let stream = Stream { source: StreamSource::Url { - url: Url::from_str(HTTP_STR_URL).unwrap(), + url: UrlExtended::Url(Url::from_str(HTTP_STR_URL).unwrap()), }, name: None, description: None, diff --git a/src/unit_tests/deep_links/stream_deep_links.rs b/src/unit_tests/deep_links/stream_deep_links.rs index 2d3e14938..9e3ca72ff 100644 --- a/src/unit_tests/deep_links/stream_deep_links.rs +++ b/src/unit_tests/deep_links/stream_deep_links.rs @@ -2,7 +2,9 @@ use crate::constants::{BASE64, URI_COMPONENT_ENCODE_SET}; use crate::deep_links::StreamDeepLinks; use crate::types::addon::{ResourcePath, ResourceRequest}; use crate::types::profile::Settings; -use crate::types::resource::{Stream, StreamBehaviorHints, StreamProxyHeaders, StreamSource}; +use crate::types::resource::{ + Stream, StreamBehaviorHints, StreamProxyHeaders, StreamSource, UrlExtended, +}; use base64::Engine; use percent_encoding::utf8_percent_encode; use std::collections::HashMap; @@ -21,7 +23,7 @@ const YT_ID: &str = "aqz-KE-bpKQ"; fn stream_deep_links_magnet() { let stream = Stream { source: StreamSource::Url { - url: Url::from_str(MAGNET_STR_URL).unwrap(), + url: UrlExtended::Url(MAGNET_STR_URL.parse().unwrap()), }, name: None, description: None, @@ -44,7 +46,7 @@ fn stream_deep_links_magnet() { fn stream_deep_links_http() { let stream = Stream { source: StreamSource::Url { - url: Url::from_str(HTTP_STR_URL).unwrap(), + url: UrlExtended::Url(HTTP_STR_URL.parse().unwrap()), }, name: None, description: None, @@ -75,7 +77,7 @@ fn stream_deep_links_http() { fn stream_deep_links_http_with_request_headers() { let stream = Stream { source: StreamSource::Url { - url: Url::from_str(HTTP_STR_URL).unwrap(), + url: UrlExtended::Url(HTTP_STR_URL.parse().unwrap()), }, name: None, description: None, @@ -110,7 +112,7 @@ fn stream_deep_links_http_with_request_headers() { fn stream_deep_links_http_with_request_response_headers_and_query_params() { let stream = Stream { source: StreamSource::Url { - url: Url::from_str(HTTP_WITH_QUERY_STR_URL).unwrap(), + url: UrlExtended::Url(HTTP_WITH_QUERY_STR_URL.parse().unwrap()), }, name: None, description: None, diff --git a/src/unit_tests/serde/stream_source.rs b/src/unit_tests/serde/stream_source.rs index fc8a92ec3..67c2adaa9 100644 --- a/src/unit_tests/serde/stream_source.rs +++ b/src/unit_tests/serde/stream_source.rs @@ -1,4 +1,4 @@ -use crate::types::resource::StreamSource; +use crate::types::resource::{StreamSource, UrlExtended}; use serde_test::{assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, Token}; use url::Url; @@ -24,7 +24,7 @@ fn stream_source() { assert_ser_tokens( &vec![ StreamSource::Url { - url: Url::parse("https://url").unwrap(), + url: UrlExtended::Url(Url::parse("https://url").unwrap()), }, StreamSource::YouTube { yt_id: "yt_id".to_owned(), @@ -112,7 +112,7 @@ fn stream_source() { assert_de_tokens( &vec![ StreamSource::Url { - url: Url::parse("https://url").unwrap(), + url: UrlExtended::Url(Url::parse("https://url").unwrap()), }, StreamSource::YouTube { yt_id: "yt_id".to_owned(), diff --git a/src/unit_tests/serde/subtitles.rs b/src/unit_tests/serde/subtitles.rs index 576e8f0f1..51f61a3e3 100644 --- a/src/unit_tests/serde/subtitles.rs +++ b/src/unit_tests/serde/subtitles.rs @@ -1,4 +1,4 @@ -use crate::types::resource::Subtitles; +use crate::types::resource::{Subtitles, UrlExtended}; use serde_test::{assert_tokens, Token}; use url::Url; @@ -7,7 +7,7 @@ fn subtitles() { assert_tokens( &Subtitles { lang: "lang".to_owned(), - url: Url::parse("https://url").unwrap(), + url: UrlExtended::Url(Url::parse("https://url").unwrap()), }, &[ Token::Struct { From 272e10a8aebcd98959bbad55b4f4a14a013ecebd Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Mon, 12 Feb 2024 18:40:07 +0200 Subject: [PATCH 3/5] fix: rustfmt and clippy Signed-off-by: Lachezar Lechev --- .../http_transport/http_transport.rs | 13 +++++++------ src/types/addon/response.rs | 17 ++++++----------- src/types/resource/stream.rs | 9 +++------ src/types/resource/subtitles.rs | 4 +++- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/addon_transport/http_transport/http_transport.rs b/src/addon_transport/http_transport/http_transport.rs index 3e79d3990..6d44ebf5a 100644 --- a/src/addon_transport/http_transport/http_transport.rs +++ b/src/addon_transport/http_transport/http_transport.rs @@ -1,15 +1,16 @@ +use std::marker::PhantomData; + +use futures::{future, FutureExt}; +use http::Request; +use percent_encoding::utf8_percent_encode; +use url::Url; + use crate::addon_transport::http_transport::legacy::AddonLegacyTransport; use crate::addon_transport::AddonTransport; use crate::constants::{ADDON_LEGACY_PATH, ADDON_MANIFEST_PATH, URI_COMPONENT_ENCODE_SET}; use crate::runtime::{Env, EnvError, EnvFutureExt, TryEnvFuture}; use crate::types::addon::{Manifest, ResourcePath, ResourceResponse}; use crate::types::query_params_encode; -use futures::{future, FutureExt, TryFutureExt}; -use http::Request; -use percent_encoding::utf8_percent_encode; -use std::future::IntoFuture; -use std::marker::PhantomData; -use url::Url; pub struct AddonHTTPTransport { transport_url: Url, diff --git a/src/types/addon/response.rs b/src/types/addon/response.rs index 4fd07b41d..bb976c7ce 100644 --- a/src/types/addon/response.rs +++ b/src/types/addon/response.rs @@ -1,5 +1,4 @@ use derive_more::TryInto; -use itertools::Itertools; use serde::{de::Deserializer, Deserialize, Serialize}; use serde_with::{serde_as, VecSkipError}; use url::Url; @@ -45,14 +44,13 @@ impl ResourceResponse { ResourceResponse::Metas { ref mut metas } => { metas .iter_mut() - .map(|meta_item_preview| { + .flat_map(|meta_item_preview| { meta_item_preview .trailer_streams .iter_mut() .filter_map(|stream| stream.with_addon_url(&addon_transport_url).ok()) // .collect::>() }) - .flatten() .collect() } ResourceResponse::MetasDetailed { @@ -60,12 +58,12 @@ impl ResourceResponse { } => { metas_detailed .iter_mut() - .map(|meta_item| { + .flat_map(|meta_item| { // MetaItem videos meta_item .videos .iter_mut() - .map(|video| { + .flat_map(|video| { // MetaItem video streams video .streams @@ -80,19 +78,17 @@ impl ResourceResponse { }), ) }) - .flatten() // Trailer Streams of the MetaItemPreview .chain(meta_item.preview.trailer_streams.iter_mut().filter_map( |stream| stream.with_addon_url(&addon_transport_url).ok(), )) }) - .flatten() .collect() } ResourceResponse::Meta { meta } => meta .videos .iter_mut() - .map(|video| { + .flat_map(|video| { // MetaItem video streams video .streams @@ -105,7 +101,6 @@ impl ResourceResponse { }), ) }) - .flatten() // Trailer Streams of the MetaItemPreview .chain( meta.preview @@ -122,9 +117,9 @@ impl ResourceResponse { .iter_mut() .filter_map(|subtitle| subtitle.url.with_addon_url(&addon_transport_url).ok()) .collect(), - ResourceResponse::Addons { addons } => { + ResourceResponse::Addons { .. } => { // for addons - do nothing - }, + } } } } diff --git a/src/types/resource/stream.rs b/src/types/resource/stream.rs index 201b2fc8b..e021b5cd3 100644 --- a/src/types/resource/stream.rs +++ b/src/types/resource/stream.rs @@ -52,12 +52,9 @@ impl UrlExtended { /// /// Otherwise, it leaves the [`UrlExtended`] unchanged. pub fn with_addon_url(&mut self, addon_transport_url: &Url) -> Result<(), ParseError> { - match &self { - UrlExtended::RelativePath(path) => { - *self = UrlExtended::Url(addon_transport_url.join(&path)?); - } - _ => {} - }; + if let UrlExtended::RelativePath(path) = &self { + *self = UrlExtended::Url(addon_transport_url.join(path)?); + } Ok(()) } diff --git a/src/types/resource/subtitles.rs b/src/types/resource/subtitles.rs index 0ca9fdcd1..3dbe50edd 100644 --- a/src/types/resource/subtitles.rs +++ b/src/types/resource/subtitles.rs @@ -11,7 +11,9 @@ pub struct Subtitles { pub lang: String, #[cfg_attr( test, - derivative(Default(value = "UrlExtended::Url(url::Url::parse(\"protocol://host\").unwrap())")) + derivative(Default( + value = "UrlExtended::Url(url::Url::parse(\"protocol://host\").unwrap())" + )) )] pub url: UrlExtended, } From 1c5c246d7d75b193d021e735329e92f915878e54 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Mon, 12 Feb 2024 18:44:09 +0200 Subject: [PATCH 4/5] feat: add convenience method to Subtitles Signed-off-by: Lachezar Lechev --- src/types/addon/response.rs | 2 +- src/types/resource/subtitles.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/types/addon/response.rs b/src/types/addon/response.rs index bb976c7ce..eebe84b24 100644 --- a/src/types/addon/response.rs +++ b/src/types/addon/response.rs @@ -115,7 +115,7 @@ impl ResourceResponse { .collect(), ResourceResponse::Subtitles { subtitles } => subtitles .iter_mut() - .filter_map(|subtitle| subtitle.url.with_addon_url(&addon_transport_url).ok()) + .filter_map(|subtitle| subtitle.with_addon_url(&addon_transport_url).ok()) .collect(), ResourceResponse::Addons { .. } => { // for addons - do nothing diff --git a/src/types/resource/subtitles.rs b/src/types/resource/subtitles.rs index 3dbe50edd..0b0ef4ed6 100644 --- a/src/types/resource/subtitles.rs +++ b/src/types/resource/subtitles.rs @@ -1,6 +1,7 @@ #[cfg(test)] use derivative::Derivative; use serde::{Deserialize, Serialize}; +use url::{ParseError, Url}; use super::UrlExtended; @@ -17,3 +18,15 @@ pub struct Subtitles { )] pub url: UrlExtended, } + +impl Subtitles { + /// This method will replace the relative path with absolute one using the provided addon transport URL, + /// only if the url is [`UrlExtended::RelativePath`]. + /// + /// Otherwise, it leaves the [`Subtitles`] unchanged. + pub fn with_addon_url(&mut self, addon_transport_url: &Url) -> Result<(), ParseError> { + self.url.with_addon_url(addon_transport_url)?; + + Ok(()) + } +} From 8eeef377180f3900e37703ebea504629c7ae57ce Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Fri, 13 Sep 2024 13:04:05 +0300 Subject: [PATCH 5/5] chore: rustfmt Signed-off-by: Lachezar Lechev --- src/types/resource/stream.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/resource/stream.rs b/src/types/resource/stream.rs index 304968a2a..9d34c6033 100644 --- a/src/types/resource/stream.rs +++ b/src/types/resource/stream.rs @@ -205,7 +205,7 @@ impl Stream { pub fn download_url(&self) -> Option { match &self.source { - StreamSource::Url { url } => match url { + StreamSource::Url { url } => match url { UrlExtended::Url(url) if url.scheme() == "magnet" => { self.magnet_url().map(|magnet_url| magnet_url.to_string()) }