diff --git a/Cargo.toml b/Cargo.toml index a7082f18b..5fd4444e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ itertools = "0.10" magnet-url = "2.0" hex = "0.4" anyhow = "1.0" +regex = "1.8" # Tracing tracing = "0.1" diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index f25e0e2c6..a5716c36d 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -6,31 +6,95 @@ use crate::models::installed_addons_with_filters::InstalledAddonsRequest; use crate::models::library_with_filters::LibraryRequest; use crate::types::addon::{ExtraValue, ResourcePath, ResourceRequest}; use crate::types::library::LibraryItem; +use crate::types::profile::Settings; use crate::types::query_params_encode; use crate::types::resource::{MetaItem, MetaItemPreview, Stream, StreamSource, Video}; use percent_encoding::utf8_percent_encode; +use regex::Regex; use serde::Serialize; use url::Url; +#[derive(Default, Serialize, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct OpenPlayerLink { + pub ios: Option, + pub android: Option, + pub windows: Option, + pub macos: Option, + pub linux: Option, + pub tizen: Option, + pub webos: Option, + pub chromeos: Option, + pub roku: Option, +} + #[derive(Default, Serialize, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ExternalPlayerLink { pub href: Option, pub download: Option, pub streaming: Option, + pub open_player: Option, pub android_tv: Option, pub tizen: Option, pub webos: Option, pub file_name: Option, } -impl From<(&Stream, &Option)> for ExternalPlayerLink { - fn from((stream, streaming_server_url): (&Stream, &Option)) -> Self { +impl From<(&Stream, &Option, &Settings)> for ExternalPlayerLink { + fn from((stream, streaming_server_url, settings): (&Stream, &Option, &Settings)) -> Self { + let http_regex = Regex::new(r"https?://").unwrap(); let download = stream.download_url(); let streaming = stream.streaming_url(streaming_server_url.as_ref()); let m3u_uri = stream.m3u_data_uri(streaming_server_url.as_ref()); let file_name = m3u_uri.as_ref().map(|_| "playlist.m3u".to_owned()); let href = m3u_uri.or_else(|| download.to_owned()); + let open_player = match &streaming { + Some(url) => match settings.player_type.as_ref() { + Some(player_type) => match player_type.as_str() { + "choose" => Some(OpenPlayerLink { + android: Some(format!( + "{}#Intent;type=video/any;scheme=https;end", + http_regex.replace(url, "intent://"), + )), + ..Default::default() + }), + "vlc" => Some(OpenPlayerLink { + ios: Some(format!("vlc-x-callback://x-callback-url/stream?url={url}")), + android: Some(format!( + "{}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", + http_regex.replace(url, "intent://"), + )), + ..Default::default() + }), + "mxplayer" => Some(OpenPlayerLink { + android: Some(format!( + "{}#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=https;end", + http_regex.replace(url, "intent://"), + )), + ..Default::default() + }), + "justplayer" => Some(OpenPlayerLink { + android: Some(format!( + "{}#Intent;package=com.brouken.player;type=video;scheme=https;end", + http_regex.replace(url, "intent://"), + )), + ..Default::default() + }), + "outplayer" => Some(OpenPlayerLink { + ios: Some(format!("{}", http_regex.replace(url, "outplayer://"))), + ..Default::default() + }), + "infuse" => Some(OpenPlayerLink { + ios: Some(format!("{}", http_regex.replace(url, "infuse://"))), + ..Default::default() + }), + _ => None, + }, + None => None, + }, + None => None, + }; let (android_tv, tizen, webos) = match &stream.source { StreamSource::External { android_tv_url, @@ -48,6 +112,7 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { href, download, streaming, + open_player, android_tv, tizen, webos, @@ -183,9 +248,14 @@ pub struct VideoDeepLinks { pub external_player: Option, } -impl From<(&Video, &ResourceRequest, &Option)> for VideoDeepLinks { +impl From<(&Video, &ResourceRequest, &Option, &Settings)> for VideoDeepLinks { fn from( - (video, request, streaming_server_url): (&Video, &ResourceRequest, &Option), + (video, request, streaming_server_url, settings): ( + &Video, + &ResourceRequest, + &Option, + &Settings, + ), ) -> Self { let stream = video.stream(); VideoDeepLinks { @@ -210,9 +280,9 @@ impl From<(&Video, &ResourceRequest, &Option)> for VideoDeepLinks { }) .transpose() .unwrap_or_else(|error| Some(ErrorLink::from(error).into())), - external_player: stream - .as_ref() - .map(|stream| ExternalPlayerLink::from((stream.as_ref(), streaming_server_url))), + external_player: stream.as_ref().map(|stream| { + ExternalPlayerLink::from((stream.as_ref(), streaming_server_url, settings)) + }), } } } @@ -224,8 +294,8 @@ pub struct StreamDeepLinks { pub external_player: ExternalPlayerLink, } -impl From<(&Stream, &Option)> for StreamDeepLinks { - fn from((stream, streaming_server_url): (&Stream, &Option)) -> Self { +impl From<(&Stream, &Option, &Settings)> for StreamDeepLinks { + fn from((stream, streaming_server_url, settings): (&Stream, &Option, &Settings)) -> Self { StreamDeepLinks { player: stream .encode() @@ -236,18 +306,27 @@ impl From<(&Stream, &Option)> for StreamDeepLinks { ) }) .unwrap_or_else(|error| ErrorLink::from(error).into()), - external_player: ExternalPlayerLink::from((stream, streaming_server_url)), + external_player: ExternalPlayerLink::from((stream, streaming_server_url, settings)), } } } -impl From<(&Stream, &ResourceRequest, &ResourceRequest, &Option)> for StreamDeepLinks { +impl + From<( + &Stream, + &ResourceRequest, + &ResourceRequest, + &Option, + &Settings, + )> for StreamDeepLinks +{ fn from( - (stream, stream_request, meta_request, streaming_server_url): ( + (stream, stream_request, meta_request, streaming_server_url, settings): ( &Stream, &ResourceRequest, &ResourceRequest, &Option, + &Settings, ), ) -> Self { StreamDeepLinks { @@ -265,7 +344,7 @@ impl From<(&Stream, &ResourceRequest, &ResourceRequest, &Option)> for Strea ) }) .unwrap_or_else(|error| ErrorLink::from(error).into()), - external_player: ExternalPlayerLink::from((stream, streaming_server_url)), + external_player: ExternalPlayerLink::from((stream, streaming_server_url, settings)), } } } diff --git a/src/unit_tests/deep_links/external_player_link.rs b/src/unit_tests/deep_links/external_player_link.rs index bff80f033..8b02be7e2 100644 --- a/src/unit_tests/deep_links/external_player_link.rs +++ b/src/unit_tests/deep_links/external_player_link.rs @@ -1,5 +1,6 @@ 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 base64::Engine; use percent_encoding::utf8_percent_encode; @@ -25,7 +26,8 @@ fn external_player_link_magnet() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(epl.href, Some(MAGNET_STR_URL.to_owned())); assert_eq!(epl.file_name, None); } @@ -43,7 +45,8 @@ fn external_player_link_http() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(epl.href, Some(BASE64_HTTP_URL.to_owned())); assert_eq!(epl.file_name, Some("playlist.m3u".to_string())); } @@ -69,7 +72,8 @@ fn external_player_link_torrent() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!( epl.href, Some(format!( @@ -109,7 +113,8 @@ fn external_player_link_external() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(epl.href, Some(HTTP_STR_URL.to_owned())); assert_eq!(epl.file_name, None); } @@ -128,7 +133,8 @@ fn external_player_link_youtube() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!( epl.href, Some(format!( @@ -155,7 +161,8 @@ fn external_player_link_player_frame() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(epl.href, Some(HTTP_STR_URL.to_owned())); assert_eq!(epl.file_name, None); } diff --git a/src/unit_tests/deep_links/stream_deep_links.rs b/src/unit_tests/deep_links/stream_deep_links.rs index b15b7c240..d6beebe8f 100644 --- a/src/unit_tests/deep_links/stream_deep_links.rs +++ b/src/unit_tests/deep_links/stream_deep_links.rs @@ -1,6 +1,7 @@ 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, StreamSource}; use base64::Engine; use percent_encoding::utf8_percent_encode; @@ -27,7 +28,8 @@ fn stream_deep_links_magnet() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(sdl.player, "stremio:///player/eAEBRgC5%2F3sidXJsIjoibWFnbmV0Oj94dD11cm46YnRpaDpkZDgyNTVlY2RjN2NhNTVmYjBiYmY4MTMyM2Q4NzA2MmRiMWY2ZDFjIn0%2BMhZF".to_string()); assert_eq!(sdl.external_player.href, Some(MAGNET_STR_URL.to_owned())); assert_eq!(sdl.external_player.file_name, None); @@ -46,7 +48,8 @@ fn stream_deep_links_http() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!( sdl.player, "stremio:///player/eAEBIQDe%2F3sidXJsIjoiaHR0cDovL2RvbWFpbi5yb290L3BhdGgifcEEC6w%3D" @@ -80,7 +83,8 @@ fn stream_deep_links_torrent() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(sdl.player, "stremio:///player/eAEBdwCI%2F3siaW5mb0hhc2giOiJkZDgyNTVlY2RjN2NhNTVmYjBiYmY4MTMyM2Q4NzA2MmRiMWY2ZDFjIiwiZmlsZUlkeCI6MCwiYW5ub3VuY2UiOlsiaHR0cDovL2J0MS5hcmNoaXZlLm9yZzo2OTY5L2Fubm91bmNlIl19ndAlsw%3D%3D".to_string()); assert_eq!( sdl.external_player.href, @@ -123,7 +127,8 @@ fn stream_deep_links_external() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(sdl.player, "stremio:///player/eAEBKQDW%2F3siZXh0ZXJuYWxVcmwiOiJodHRwOi8vZG9tYWluLnJvb3QvcGF0aCJ9OoEO7w%3D%3D".to_string()); assert_eq!(sdl.external_player.href, Some(HTTP_STR_URL.to_owned())); assert_eq!(sdl.external_player.file_name, None); @@ -142,7 +147,8 @@ fn stream_deep_links_youtube() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!( sdl.player, "stremio:///player/eAEBFgDp%2F3sieXRJZCI6ImFxei1LRS1icEtRIn1RRQb5".to_string() @@ -176,7 +182,8 @@ fn stream_deep_links_player_frame() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(sdl.player, "stremio:///player/eAEBLADT%2F3sicGxheWVyRnJhbWVVcmwiOiJodHRwOi8vZG9tYWluLnJvb3QvcGF0aCJ9abUQBA%3D%3D".to_string()); assert_eq!(sdl.external_player.href, Some(HTTP_STR_URL.to_owned())); assert_eq!(sdl.external_player.file_name, None); @@ -204,11 +211,13 @@ fn stream_deep_links_requests() { }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); + let settings = Settings::default(); let sdl = StreamDeepLinks::try_from(( &stream, &stream_request, &meta_request, &streaming_server_url, + &settings, )) .unwrap(); assert_eq!(sdl.player, format!( diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index ef5d049d0..6307e913a 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -1,6 +1,7 @@ use crate::constants::BASE64; use crate::deep_links::{ExternalPlayerLink, VideoDeepLinks}; use crate::types::addon::{ResourcePath, ResourceRequest}; +use crate::types::profile::Settings; use crate::types::resource::Video; use base64::Engine; use std::convert::TryFrom; @@ -27,7 +28,9 @@ fn video_deep_links() { path: ResourcePath::without_extra("meta", "movie", format!("yt_id:{YT_ID}").as_str()), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let vdl = VideoDeepLinks::try_from((&video, &request, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let vdl = + VideoDeepLinks::try_from((&video, &request, &streaming_server_url, &settings)).unwrap(); assert_eq!( vdl.meta_details_streams, format!(