From be6da5e7e3558ef39773bf45bcb8afbf006bacec Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Wed, 23 Apr 2025 21:21:23 +0200 Subject: [PATCH] feat: player: handle VPN ban and captcha required error messages --- src/client/player.rs | 34 +++++++++++++++++++--------------- src/client/response/player.rs | 13 ++++++++----- src/error.rs | 9 +++++++++ 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/client/player.rs b/src/client/player.rs index 42f7759..9bae601 100644 --- a/src/client/player.rs +++ b/src/client/player.rs @@ -307,24 +307,28 @@ impl MapResponse for response::Player { error_screen, } => { let mut msg = reason; - if let Some(error_screen) = error_screen { + if let Some(error_screen) = error_screen.player_error_message_renderer { msg.push_str(" - "); - msg.push_str(&error_screen.player_error_message_renderer.subreason); + msg.push_str(&error_screen.subreason); } - let reason = msg - .split_whitespace() - .find_map(|word| match word { - "payment" => Some(UnavailabilityReason::Paid), - "Premium" => Some(UnavailabilityReason::Premium), - "members-only" => Some(UnavailabilityReason::MembersOnly), - "country" => Some(UnavailabilityReason::Geoblocked), - "version" | "websites" => Some(UnavailabilityReason::UnsupportedClient), - "bot" => Some(UnavailabilityReason::IpBan), - "later." => Some(UnavailabilityReason::TryAgain), - _ => None, - }) - .unwrap_or_default(); + let reason = if error_screen.player_captcha_view_model.is_some() { + UnavailabilityReason::Captcha + } else { + msg.split_whitespace() + .find_map(|word| match word { + "payment" => Some(UnavailabilityReason::Paid), + "Premium" => Some(UnavailabilityReason::Premium), + "members-only" => Some(UnavailabilityReason::MembersOnly), + "country" => Some(UnavailabilityReason::Geoblocked), + "version" | "websites" => Some(UnavailabilityReason::UnsupportedClient), + "bot" => Some(UnavailabilityReason::IpBan), + "VPN/Proxy" => Some(UnavailabilityReason::VpnBan), + "later." => Some(UnavailabilityReason::TryAgain), + _ => None, + }) + .unwrap_or_default() + }; return Err(ExtractionError::Unavailable { reason, msg }); } response::player::PlayabilityStatus::LoginRequired { reason, messages } => { diff --git a/src/client/response/player.rs b/src/client/response/player.rs index 4fbcb28..a880dd5 100644 --- a/src/client/response/player.rs +++ b/src/client/response/player.rs @@ -37,8 +37,7 @@ pub(crate) enum PlayabilityStatus { #[serde(default)] reason: String, #[serde(default)] - #[serde_as(deserialize_as = "DefaultOnError")] - error_screen: Option, + error_screen: ErrorScreen, }, /// Age limit / Private video #[serde(rename_all = "camelCase")] @@ -61,14 +60,18 @@ pub(crate) enum PlayabilityStatus { }, } -#[derive(Debug, Deserialize)] +#[serde_as] +#[derive(Default, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ErrorScreen { - pub player_error_message_renderer: ErrorMessage, + #[serde(default)] + #[serde_as(deserialize_as = "DefaultOnError")] + pub player_error_message_renderer: Option, + pub player_captcha_view_model: Option, } #[serde_as] -#[derive(Debug, Deserialize)] +#[derive(Default, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ErrorMessage { #[serde_as(as = "Text")] diff --git a/src/error.rs b/src/error.rs index bb6c769..81cd7fa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -105,6 +105,13 @@ pub enum UnavailabilityReason { OfflineLivestream, /// YouTube banned your IP address from accessing the platform without an account IpBan, + /// YouTube bans IP addresses from certain VPN providers from accessing certain geo-restricted + /// videos. + /// + /// If this happens to you, you can try another server / VPN provider or disable your VPN. + VpnBan, + /// YouTube requires the user to solve a ReCaptcha + Captcha, /// Video temporarily unavailable (rate limit) TryAgain, /// Video cant be played for other reasons @@ -125,6 +132,8 @@ impl Display for UnavailabilityReason { UnavailabilityReason::MembersOnly => f.write_str("members-only"), UnavailabilityReason::OfflineLivestream => f.write_str("offline stream"), UnavailabilityReason::IpBan => f.write_str("ip-ban"), + UnavailabilityReason::VpnBan => f.write_str("vpn-ban"), + UnavailabilityReason::Captcha => f.write_str("captcha"), UnavailabilityReason::TryAgain => f.write_str("try again"), UnavailabilityReason::Unplayable => f.write_str("unplayable"), }