//! RustyPipe error types use std::{borrow::Cow, fmt::Display}; use reqwest::StatusCode; /// Error type for the RustyPipe library #[derive(thiserror::Error, Debug)] #[non_exhaustive] pub enum Error { /// Error extracting content from YouTube #[error("extraction error: {0}")] Extraction(#[from] ExtractionError), /// Error from the HTTP client #[error("http error: {0}")] Http(Cow<'static, str>), /// Erroneous HTTP status code received #[error("http status code: {0} message: {1}")] HttpStatus(u16, Cow<'static, str>), /// Unspecified error #[error("error: {0}")] Other(Cow<'static, str>), } /// Error extracting content from YouTube #[derive(thiserror::Error, Debug)] #[non_exhaustive] pub enum ExtractionError { /// Video cannot be extracted with RustyPipe /// /// Reasons include: /// - Deletion/Censorship /// - Private video that requires a Google account /// - DRM (Movies and TV shows) #[error("video cant be played because it is {reason}. Reason (from YT): {msg}")] VideoUnavailable { /// Reason why the video could not be extracted reason: UnavailabilityReason, /// The error message as returned from YouTube msg: String, }, /// Content with the given ID does not exist #[error("content `{id}` was not found ({msg})")] NotFound { /// ID of the requested content id: String, /// Error message msg: Cow<'static, str>, }, /// Bad request (Error 400 from YouTube), probably invalid input parameters #[error("bad request ({0})")] BadRequest(Cow<'static, str>), /// YouTube returned data that could not be deserialized or parsed #[error("invalid data from YT: {0}")] InvalidData(Cow<'static, str>), /// Error deobfuscating YouTube's URL signatures #[error("deobfuscation error: {0}")] Deobfuscation(Cow<'static, str>), /// YouTube returned data that does not match the queried ID /// /// Specifically YouTube may return this video , /// which is a 5 minute error message, instead of the requested video when using an outdated /// Android client. #[error("wrong result from YT: {0}")] WrongResult(String), /// YouTube redirects you to another content ID /// /// This is used internally for YouTube Music channels that link to a main channel. #[error("redirecting to: {0}")] Redirect(String), /// Warnings occurred during deserialization/mapping /// /// This error is only returned in strict mode. #[error("warnings during deserialization/mapping")] DeserializationWarnings, } /// Reason why a video cannot be extracted #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum UnavailabilityReason { /// Video is age restricted. /// /// Age restriction may be circumvented with the /// [`ClientType::TvHtml5Embed`](crate::client::ClientType::TvHtml5Embed) client. AgeRestricted, /// Video was deleted or censored Deleted, /// Video is not available in your country Geoblocked, /// Video cannot be extracted with the specified client UnsupportedClient, /// Video is private Private, /// Video needs to be purchased and is protected by digital restrictions management /// (e.g. movies and TV shows) Paid, /// Video is only available to YouTube Premium users Premium, /// Video is only available to channel members MembersOnly, /// Livestream has gone offline OfflineLivestream, /// Video cant be played for other reasons #[default] Unplayable, } impl Display for UnavailabilityReason { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { UnavailabilityReason::AgeRestricted => f.write_str("age restriction"), UnavailabilityReason::Deleted => f.write_str("deleted"), UnavailabilityReason::Geoblocked => f.write_str("geoblocking"), UnavailabilityReason::UnsupportedClient => f.write_str("unsupported by client"), UnavailabilityReason::Private => f.write_str("private"), UnavailabilityReason::Paid => f.write_str("paid"), UnavailabilityReason::Premium => f.write_str("premium-only"), UnavailabilityReason::MembersOnly => f.write_str("members-only"), UnavailabilityReason::OfflineLivestream => f.write_str("an offline stream"), UnavailabilityReason::Unplayable => f.write_str("unplayable"), } } } pub(crate) mod internal { use super::*; /// Error that occurred during the initialization /// or use of the YouTube URL signature deobfuscator. #[derive(thiserror::Error, Debug)] pub enum DeobfError { /// Error during JavaScript execution #[error("js execution error: {0}")] JavaScript(#[from] quick_js::ExecutionError), /// Error during JavaScript parsing #[error("js parsing: {0}")] JsParser(#[from] ress::error::Error), /// Could not extract certain data #[error("could not extract {0}")] Extraction(&'static str), /// Unspecified error #[error("error: {0}")] Other(&'static str), } impl From for Error { fn from(value: DeobfError) -> Self { Self::Extraction(value.into()) } } impl From for ExtractionError { fn from(value: DeobfError) -> Self { Self::Deobfuscation(value.to_string().into()) } } } impl From for ExtractionError { fn from(value: serde_json::Error) -> Self { Self::InvalidData(value.to_string().into()) } } impl From for Error { fn from(value: reqwest::Error) -> Self { if value.is_status() { if let Some(status) = value.status() { return Self::HttpStatus(status.as_u16(), Default::default()); } } Self::Http(value.to_string().into()) } } impl From for Error { fn from(value: serde_plain::Error) -> Self { Self::Other(value.to_string().into()) } } impl Error { /// Return true if a report should be generated pub(crate) fn should_report(&self) -> bool { matches!( self, Self::HttpStatus(_, _) | Self::Extraction(ExtractionError::InvalidData(_)) | Self::Extraction(ExtractionError::WrongResult(_)) ) } /// Return true if the request should be retried pub(crate) fn should_retry(&self) -> bool { match self { Self::HttpStatus(code, _) => match StatusCode::try_from(*code) { Ok(status) => status.is_server_error() || status == StatusCode::TOO_MANY_REQUESTS, Err(_) => false, }, Self::Extraction(ExtractionError::InvalidData(_)) => true, _ => false, } } } impl ExtractionError { /// Return true if the video should be fetched with a different client pub(crate) fn switch_client(&self) -> bool { matches!( self, ExtractionError::VideoUnavailable { reason: UnavailabilityReason::AgeRestricted | UnavailabilityReason::UnsupportedClient, .. } | ExtractionError::WrongResult(_) ) } }