refactor: make DeobfError private
This commit is contained in:
parent
d7caba81d0
commit
6ab7b2415a
3 changed files with 56 additions and 44 deletions
|
|
@ -10,7 +10,7 @@ use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
deobfuscate::Deobfuscator,
|
deobfuscate::Deobfuscator,
|
||||||
error::{DeobfError, Error, ExtractionError},
|
error::{internal::DeobfError, Error, ExtractionError},
|
||||||
model::{
|
model::{
|
||||||
traits::QualityOrd, AudioCodec, AudioFormat, AudioStream, AudioTrack, ChannelId, Subtitle,
|
traits::QualityOrd, AudioCodec, AudioFormat, AudioStream, AudioTrack, ChannelId, Subtitle,
|
||||||
VideoCodec, VideoFormat, VideoPlayer, VideoPlayerDetails, VideoStream,
|
VideoCodec, VideoFormat, VideoPlayer, VideoPlayerDetails, VideoStream,
|
||||||
|
|
@ -143,8 +143,7 @@ impl MapResponse<VideoPlayer> for response::Player {
|
||||||
_lang: Language,
|
_lang: Language,
|
||||||
deobf: Option<&crate::deobfuscate::DeobfData>,
|
deobf: Option<&crate::deobfuscate::DeobfData>,
|
||||||
) -> Result<super::MapResult<VideoPlayer>, ExtractionError> {
|
) -> Result<super::MapResult<VideoPlayer>, ExtractionError> {
|
||||||
let deobf = Deobfuscator::new(deobf.unwrap())
|
let deobf = Deobfuscator::new(deobf.unwrap())?;
|
||||||
.map_err(|e| ExtractionError::InvalidData(e.to_string().into()))?;
|
|
||||||
let mut warnings = vec![];
|
let mut warnings = vec![];
|
||||||
|
|
||||||
// Check playability status
|
// Check playability status
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ use regex::Regex;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{error::DeobfError, util};
|
use crate::{
|
||||||
|
error::{internal::DeobfError, Error},
|
||||||
type Result<T> = core::result::Result<T, DeobfError>;
|
util,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Deobfuscator {
|
pub struct Deobfuscator {
|
||||||
ctx: quick_js::Context,
|
ctx: quick_js::Context,
|
||||||
|
|
@ -21,7 +22,7 @@ pub struct DeobfData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeobfData {
|
impl DeobfData {
|
||||||
pub async fn download(http: Client) -> Result<Self> {
|
pub async fn download(http: Client) -> Result<Self, Error> {
|
||||||
let js_url = get_player_js_url(&http).await?;
|
let js_url = get_player_js_url(&http).await?;
|
||||||
let player_js = get_response(&http, &js_url).await?;
|
let player_js = get_response(&http, &js_url).await?;
|
||||||
|
|
||||||
|
|
@ -41,7 +42,7 @@ impl DeobfData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deobfuscator {
|
impl Deobfuscator {
|
||||||
pub fn new(data: &DeobfData) -> Result<Self> {
|
pub fn new(data: &DeobfData) -> Result<Self, DeobfError> {
|
||||||
let ctx =
|
let ctx =
|
||||||
quick_js::Context::new().or(Err(DeobfError::Other("could not create QuickJS rt")))?;
|
quick_js::Context::new().or(Err(DeobfError::Other("could not create QuickJS rt")))?;
|
||||||
ctx.eval(&data.sig_fn)?;
|
ctx.eval(&data.sig_fn)?;
|
||||||
|
|
@ -50,7 +51,7 @@ impl Deobfuscator {
|
||||||
Ok(Self { ctx })
|
Ok(Self { ctx })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deobfuscate_sig(&self, sig: &str) -> Result<String> {
|
pub fn deobfuscate_sig(&self, sig: &str) -> Result<String, DeobfError> {
|
||||||
let res = self.ctx.call_function(DEOBF_SIG_FUNC_NAME, vec![sig])?;
|
let res = self.ctx.call_function(DEOBF_SIG_FUNC_NAME, vec![sig])?;
|
||||||
|
|
||||||
res.as_str().map_or(
|
res.as_str().map_or(
|
||||||
|
|
@ -62,7 +63,7 @@ impl Deobfuscator {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deobfuscate_nsig(&self, nsig: &str) -> Result<String> {
|
pub fn deobfuscate_nsig(&self, nsig: &str) -> Result<String, DeobfError> {
|
||||||
let res = self.ctx.call_function(DEOBF_NSIG_FUNC_NAME, vec![nsig])?;
|
let res = self.ctx.call_function(DEOBF_NSIG_FUNC_NAME, vec![nsig])?;
|
||||||
|
|
||||||
res.as_str().map_or(
|
res.as_str().map_or(
|
||||||
|
|
@ -78,7 +79,7 @@ impl Deobfuscator {
|
||||||
const DEOBF_SIG_FUNC_NAME: &str = "deobf_sig";
|
const DEOBF_SIG_FUNC_NAME: &str = "deobf_sig";
|
||||||
const DEOBF_NSIG_FUNC_NAME: &str = "deobf_nsig";
|
const DEOBF_NSIG_FUNC_NAME: &str = "deobf_nsig";
|
||||||
|
|
||||||
fn get_sig_fn_name(player_js: &str) -> Result<String> {
|
fn get_sig_fn_name(player_js: &str) -> Result<String, DeobfError> {
|
||||||
static FUNCTION_REGEXES: Lazy<[FancyRegex; 6]> = Lazy::new(|| {
|
static FUNCTION_REGEXES: Lazy<[FancyRegex; 6]> = Lazy::new(|| {
|
||||||
[
|
[
|
||||||
FancyRegex::new("(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*\"\"\\s*\\)").unwrap(),
|
FancyRegex::new("(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*\"\"\\s*\\)").unwrap(),
|
||||||
|
|
@ -98,7 +99,7 @@ fn caller_function(mapped_name: &str, fn_name: &str) -> String {
|
||||||
format!("var {mapped_name}={fn_name};")
|
format!("var {mapped_name}={fn_name};")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sig_fn(player_js: &str) -> Result<String> {
|
fn get_sig_fn(player_js: &str) -> Result<String, DeobfError> {
|
||||||
let dfunc_name = get_sig_fn_name(player_js)?;
|
let dfunc_name = get_sig_fn_name(player_js)?;
|
||||||
|
|
||||||
let function_pattern_str =
|
let function_pattern_str =
|
||||||
|
|
@ -141,7 +142,7 @@ fn get_sig_fn(player_js: &str) -> Result<String> {
|
||||||
+ &caller_function(DEOBF_SIG_FUNC_NAME, &dfunc_name))
|
+ &caller_function(DEOBF_SIG_FUNC_NAME, &dfunc_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_nsig_fn_name(player_js: &str) -> Result<String> {
|
fn get_nsig_fn_name(player_js: &str) -> Result<String, DeobfError> {
|
||||||
static FUNCTION_NAME_REGEX: Lazy<Regex> = Lazy::new(|| {
|
static FUNCTION_NAME_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||||
Regex::new("\\.get\\(\"n\"\\)\\)&&\\([a-zA-Z0-9$_]=([a-zA-Z0-9$_]+)(?:\\[(\\d+)])?\\([a-zA-Z0-9$_]\\)")
|
Regex::new("\\.get\\(\"n\"\\)\\)&&\\([a-zA-Z0-9$_]=([a-zA-Z0-9$_]+)(?:\\[(\\d+)])?\\([a-zA-Z0-9$_]\\)")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -183,7 +184,7 @@ fn get_nsig_fn_name(player_js: &str) -> Result<String> {
|
||||||
Ok(name.to_owned())
|
Ok(name.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_js_fn(js: &str, name: &str) -> Result<String> {
|
fn extract_js_fn(js: &str, name: &str) -> Result<String, DeobfError> {
|
||||||
let scan = ress::Scanner::new(js);
|
let scan = ress::Scanner::new(js);
|
||||||
let mut state = 0;
|
let mut state = 0;
|
||||||
let mut level = 0;
|
let mut level = 0;
|
||||||
|
|
@ -235,7 +236,7 @@ fn extract_js_fn(js: &str, name: &str) -> Result<String> {
|
||||||
Ok(js[start..end].to_owned())
|
Ok(js[start..end].to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_nsig_fn(player_js: &str) -> Result<String> {
|
fn get_nsig_fn(player_js: &str) -> Result<String, DeobfError> {
|
||||||
let function_name = get_nsig_fn_name(player_js)?;
|
let function_name = get_nsig_fn_name(player_js)?;
|
||||||
let function_base = function_name.to_owned() + "=function";
|
let function_base = function_name.to_owned() + "=function";
|
||||||
let offset = player_js.find(&function_base).unwrap_or_default();
|
let offset = player_js.find(&function_base).unwrap_or_default();
|
||||||
|
|
@ -244,7 +245,7 @@ fn get_nsig_fn(player_js: &str) -> Result<String> {
|
||||||
.map(|s| s + ";" + &caller_function(DEOBF_NSIG_FUNC_NAME, &function_name))
|
.map(|s| s + ";" + &caller_function(DEOBF_NSIG_FUNC_NAME, &function_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_player_js_url(http: &Client) -> Result<String> {
|
async fn get_player_js_url(http: &Client) -> Result<String, Error> {
|
||||||
let resp = http
|
let resp = http
|
||||||
.get("https://www.youtube.com/iframe_api")
|
.get("https://www.youtube.com/iframe_api")
|
||||||
.send()
|
.send()
|
||||||
|
|
@ -267,12 +268,12 @@ async fn get_player_js_url(http: &Client) -> Result<String> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_response(http: &Client, url: &str) -> Result<String> {
|
async fn get_response(http: &Client, url: &str) -> Result<String, Error> {
|
||||||
let resp = http.get(url).send().await?.error_for_status()?;
|
let resp = http.get(url).send().await?.error_for_status()?;
|
||||||
Ok(resp.text().await?)
|
Ok(resp.text().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sts(player_js: &str) -> Result<String> {
|
fn get_sts(player_js: &str) -> Result<String, DeobfError> {
|
||||||
static STS_PATTERN: Lazy<Regex> =
|
static STS_PATTERN: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new("signatureTimestamp[=:](\\d+)").unwrap());
|
Lazy::new(|| Regex::new("signatureTimestamp[=:](\\d+)").unwrap());
|
||||||
|
|
||||||
|
|
|
||||||
64
src/error.rs
64
src/error.rs
|
|
@ -2,16 +2,13 @@
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// Custom error type for the RustyPipe library
|
/// Error type for the RustyPipe library
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Error extracting content from YouTube
|
/// Error extracting content from YouTube
|
||||||
#[error("extraction error: {0}")]
|
#[error("extraction error: {0}")]
|
||||||
Extraction(#[from] ExtractionError),
|
Extraction(#[from] ExtractionError),
|
||||||
/// Error from the deobfuscater
|
|
||||||
#[error("deobfuscator error: {0}")]
|
|
||||||
Deobfuscation(#[from] DeobfError),
|
|
||||||
/// File IO error
|
/// File IO error
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
|
|
@ -26,28 +23,6 @@ pub enum Error {
|
||||||
Other(Cow<'static, str>),
|
Other(Cow<'static, str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error that occurred during the initialization
|
|
||||||
/// or use of the YouTube URL signature deobfuscator.
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum DeobfError {
|
|
||||||
/// Error from the HTTP client
|
|
||||||
#[error("http error: {0}")]
|
|
||||||
Http(#[from] reqwest::Error),
|
|
||||||
/// 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),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error extracting content from YouTube
|
/// Error extracting content from YouTube
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
|
@ -80,6 +55,9 @@ pub enum ExtractionError {
|
||||||
/// Error deserializing YouTube's response JSON
|
/// Error deserializing YouTube's response JSON
|
||||||
#[error("deserialization error: {0}")]
|
#[error("deserialization error: {0}")]
|
||||||
Deserialization(#[from] serde_json::Error),
|
Deserialization(#[from] serde_json::Error),
|
||||||
|
/// Error deobfuscating YouTube's URL signatures
|
||||||
|
#[error("deobfuscation error: {0}")]
|
||||||
|
Deobfuscation(Cow<'static, str>),
|
||||||
/// YouTube returned invalid data
|
/// YouTube returned invalid data
|
||||||
#[error("got invalid data from YT: {0}")]
|
#[error("got invalid data from YT: {0}")]
|
||||||
InvalidData(Cow<'static, str>),
|
InvalidData(Cow<'static, str>),
|
||||||
|
|
@ -102,6 +80,40 @@ pub enum ExtractionError {
|
||||||
DeserializationWarnings,
|
DeserializationWarnings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<DeobfError> for Error {
|
||||||
|
fn from(value: DeobfError) -> Self {
|
||||||
|
Self::Extraction(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DeobfError> for ExtractionError {
|
||||||
|
fn from(value: DeobfError) -> Self {
|
||||||
|
Self::Deobfuscation(value.to_string().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ExtractionError {
|
impl ExtractionError {
|
||||||
pub(crate) fn should_report(&self) -> bool {
|
pub(crate) fn should_report(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
|
|
|
||||||
Reference in a new issue