fix: update client versions, enable Opus audio with iOS client
This commit is contained in:
parent
cc87b499d7
commit
31677181d5
2 changed files with 72 additions and 57 deletions
|
|
@ -83,14 +83,8 @@ pub enum ClientType {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientType {
|
impl ClientType {
|
||||||
fn is_web(self) -> bool {
|
fn needs_deobf(self) -> bool {
|
||||||
match self {
|
!matches!(self, ClientType::Ios)
|
||||||
ClientType::Desktop
|
|
||||||
| ClientType::DesktopMusic
|
|
||||||
| ClientType::Mobile
|
|
||||||
| ClientType::Tv => true,
|
|
||||||
ClientType::Android | ClientType::Ios => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,13 +107,19 @@ struct YTContext<'a> {
|
||||||
struct ClientInfo<'a> {
|
struct ClientInfo<'a> {
|
||||||
client_name: &'a str,
|
client_name: &'a str,
|
||||||
client_version: Cow<'a, str>,
|
client_version: Cow<'a, str>,
|
||||||
|
#[serde(skip_serializing_if = "str::is_empty")]
|
||||||
|
client_screen: &'a str,
|
||||||
|
#[serde(skip_serializing_if = "str::is_empty")]
|
||||||
|
device_model: &'a str,
|
||||||
|
#[serde(skip_serializing_if = "str::is_empty")]
|
||||||
|
os_name: &'a str,
|
||||||
|
#[serde(skip_serializing_if = "str::is_empty")]
|
||||||
|
os_version: &'a str,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
client_screen: Option<&'a str>,
|
android_sdk_version: Option<u8>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
device_model: Option<&'a str>,
|
|
||||||
platform: &'a str,
|
platform: &'a str,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "str::is_empty")]
|
||||||
original_url: Option<&'a str>,
|
original_url: &'a str,
|
||||||
visitor_data: &'a str,
|
visitor_data: &'a str,
|
||||||
hl: Language,
|
hl: Language,
|
||||||
gl: Country,
|
gl: Country,
|
||||||
|
|
@ -132,10 +132,13 @@ impl Default for ClientInfo<'_> {
|
||||||
Self {
|
Self {
|
||||||
client_name: "",
|
client_name: "",
|
||||||
client_version: Cow::default(),
|
client_version: Cow::default(),
|
||||||
client_screen: None,
|
client_screen: "",
|
||||||
device_model: None,
|
device_model: "",
|
||||||
|
os_name: "",
|
||||||
|
os_version: "",
|
||||||
|
android_sdk_version: None,
|
||||||
platform: "",
|
platform: "",
|
||||||
original_url: None,
|
original_url: "",
|
||||||
visitor_data: "",
|
visitor_data: "",
|
||||||
hl: Language::En,
|
hl: Language::En,
|
||||||
gl: Country::Us,
|
gl: Country::Us,
|
||||||
|
|
@ -298,14 +301,14 @@ const YOUTUBE_TV_URL: &str = "https://www.youtube.com/tv";
|
||||||
const DISABLE_PRETTY_PRINT_PARAMETER: &str = "prettyPrint=false";
|
const DISABLE_PRETTY_PRINT_PARAMETER: &str = "prettyPrint=false";
|
||||||
|
|
||||||
// Web client
|
// Web client
|
||||||
const DESKTOP_CLIENT_VERSION: &str = "2.20241010.09.00";
|
const DESKTOP_CLIENT_VERSION: &str = "2.20241216.05.00";
|
||||||
const DESKTOP_MUSIC_CLIENT_VERSION: &str = "1.20241007.00.00";
|
const DESKTOP_MUSIC_CLIENT_VERSION: &str = "1.20241216.01.00";
|
||||||
const MOBILE_CLIENT_VERSION: &str = "2.20241011.01.00";
|
const MOBILE_CLIENT_VERSION: &str = "2.20241217.07.00";
|
||||||
const TV_CLIENT_VERSION: &str = "7.20241008.14.02";
|
const TV_CLIENT_VERSION: &str = "7.20241211.14.00";
|
||||||
|
|
||||||
// Mobile app client
|
// Mobile app client
|
||||||
const APP_CLIENT_VERSION: &str = "18.03.33";
|
const APP_CLIENT_VERSION: &str = "19.44.38";
|
||||||
const IOS_DEVICE_MODEL: &str = "iPhone14,5";
|
const IOS_DEVICE_MODEL: &str = "iPhone16,2";
|
||||||
|
|
||||||
const OAUTH_CLIENT_ID: &str =
|
const OAUTH_CLIENT_ID: &str =
|
||||||
"861556708454-d6dlm3lh05idd8npek18k6be8ba3oc68.apps.googleusercontent.com";
|
"861556708454-d6dlm3lh05idd8npek18k6be8ba3oc68.apps.googleusercontent.com";
|
||||||
|
|
@ -321,7 +324,8 @@ static VISITOR_DATA_REGEX: Lazy<Regex> =
|
||||||
///
|
///
|
||||||
/// The order may change in the future in case YouTube applies changes to their
|
/// The order may change in the future in case YouTube applies changes to their
|
||||||
/// platform that disable a client or make it less reliable.
|
/// platform that disable a client or make it less reliable.
|
||||||
pub const DEFAULT_PLAYER_CLIENT_ORDER: &[ClientType] = &[ClientType::Tv, ClientType::Ios];
|
pub const DEFAULT_PLAYER_CLIENT_ORDER: &[ClientType] =
|
||||||
|
&[ClientType::Ios, ClientType::Tv, ClientType::Android];
|
||||||
|
|
||||||
/// The RustyPipe client used to access YouTube's API
|
/// The RustyPipe client used to access YouTube's API
|
||||||
///
|
///
|
||||||
|
|
@ -1505,13 +1509,13 @@ impl RustyPipeQuery {
|
||||||
ClientType::Mobile => MOBILE_UA.into(),
|
ClientType::Mobile => MOBILE_UA.into(),
|
||||||
ClientType::Tv => TV_UA.into(),
|
ClientType::Tv => TV_UA.into(),
|
||||||
ClientType::Android => format!(
|
ClientType::Android => format!(
|
||||||
"com.google.android.youtube/{} (Linux; U; Android 12; {}) gzip",
|
"com.google.android.youtube/{} (Linux; U; Android 11) gzip",
|
||||||
APP_CLIENT_VERSION, self.opts.country
|
APP_CLIENT_VERSION
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
ClientType::Ios => format!(
|
ClientType::Ios => format!(
|
||||||
"com.google.ios.youtube/{} ({}; U; CPU iOS 15_4 like Mac OS X; {})",
|
"com.google.ios.youtube/{} ({}; U; CPU iOS 18_1_0 like Mac OS X)",
|
||||||
APP_CLIENT_VERSION, IOS_DEVICE_MODEL, self.opts.country
|
APP_CLIENT_VERSION, IOS_DEVICE_MODEL
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
}
|
}
|
||||||
|
|
@ -1550,7 +1554,7 @@ impl RustyPipeQuery {
|
||||||
client_name: "WEB",
|
client_name: "WEB",
|
||||||
client_version: self.client.get_client_version(ctype).await,
|
client_version: self.client.get_client_version(ctype).await,
|
||||||
platform: "DESKTOP",
|
platform: "DESKTOP",
|
||||||
original_url: Some(YOUTUBE_HOME_URL),
|
original_url: YOUTUBE_HOME_URL,
|
||||||
visitor_data,
|
visitor_data,
|
||||||
hl,
|
hl,
|
||||||
gl,
|
gl,
|
||||||
|
|
@ -1565,7 +1569,7 @@ impl RustyPipeQuery {
|
||||||
client_name: "WEB_REMIX",
|
client_name: "WEB_REMIX",
|
||||||
client_version: self.client.get_client_version(ctype).await,
|
client_version: self.client.get_client_version(ctype).await,
|
||||||
platform: "DESKTOP",
|
platform: "DESKTOP",
|
||||||
original_url: Some(YOUTUBE_MUSIC_HOME_URL),
|
original_url: YOUTUBE_MUSIC_HOME_URL,
|
||||||
visitor_data,
|
visitor_data,
|
||||||
hl,
|
hl,
|
||||||
gl,
|
gl,
|
||||||
|
|
@ -1580,7 +1584,7 @@ impl RustyPipeQuery {
|
||||||
client_name: "MWEB",
|
client_name: "MWEB",
|
||||||
client_version: self.client.get_client_version(ctype).await,
|
client_version: self.client.get_client_version(ctype).await,
|
||||||
platform: "MOBILE",
|
platform: "MOBILE",
|
||||||
original_url: Some(YOUTUBE_MOBILE_HOME_URL),
|
original_url: YOUTUBE_MOBILE_HOME_URL,
|
||||||
visitor_data,
|
visitor_data,
|
||||||
hl,
|
hl,
|
||||||
gl,
|
gl,
|
||||||
|
|
@ -1594,9 +1598,9 @@ impl RustyPipeQuery {
|
||||||
client: ClientInfo {
|
client: ClientInfo {
|
||||||
client_name: "TVHTML5",
|
client_name: "TVHTML5",
|
||||||
client_version: self.client.get_client_version(ctype).await,
|
client_version: self.client.get_client_version(ctype).await,
|
||||||
client_screen: Some("WATCH"),
|
client_screen: "WATCH",
|
||||||
platform: "TV",
|
platform: "TV",
|
||||||
device_model: Some("SmartTV"),
|
device_model: "SmartTV",
|
||||||
visitor_data,
|
visitor_data,
|
||||||
hl,
|
hl,
|
||||||
gl,
|
gl,
|
||||||
|
|
@ -1612,6 +1616,9 @@ impl RustyPipeQuery {
|
||||||
client: ClientInfo {
|
client: ClientInfo {
|
||||||
client_name: "ANDROID",
|
client_name: "ANDROID",
|
||||||
client_version: APP_CLIENT_VERSION.into(),
|
client_version: APP_CLIENT_VERSION.into(),
|
||||||
|
os_name: "Android",
|
||||||
|
os_version: "11",
|
||||||
|
android_sdk_version: Some(30),
|
||||||
platform: "MOBILE",
|
platform: "MOBILE",
|
||||||
visitor_data,
|
visitor_data,
|
||||||
hl,
|
hl,
|
||||||
|
|
@ -1626,7 +1633,9 @@ impl RustyPipeQuery {
|
||||||
client: ClientInfo {
|
client: ClientInfo {
|
||||||
client_name: "IOS",
|
client_name: "IOS",
|
||||||
client_version: APP_CLIENT_VERSION.into(),
|
client_version: APP_CLIENT_VERSION.into(),
|
||||||
device_model: Some(IOS_DEVICE_MODEL),
|
device_model: IOS_DEVICE_MODEL,
|
||||||
|
os_name: "iPhone",
|
||||||
|
os_version: "18.1.0.22B83",
|
||||||
platform: "MOBILE",
|
platform: "MOBILE",
|
||||||
visitor_data,
|
visitor_data,
|
||||||
hl,
|
hl,
|
||||||
|
|
@ -1721,6 +1730,7 @@ impl RustyPipeQuery {
|
||||||
.post(format!(
|
.post(format!(
|
||||||
"{YOUTUBEI_V1_GAPIS_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}"
|
"{YOUTUBEI_V1_GAPIS_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}"
|
||||||
))
|
))
|
||||||
|
.header("X-YouTube-Client-Name", "3")
|
||||||
.header("X-Goog-Api-Format-Version", "2"),
|
.header("X-Goog-Api-Format-Version", "2"),
|
||||||
ClientType::Ios => self
|
ClientType::Ios => self
|
||||||
.client
|
.client
|
||||||
|
|
@ -1729,9 +1739,12 @@ impl RustyPipeQuery {
|
||||||
.post(format!(
|
.post(format!(
|
||||||
"{YOUTUBEI_V1_GAPIS_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}"
|
"{YOUTUBEI_V1_GAPIS_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}"
|
||||||
))
|
))
|
||||||
|
.header("X-YouTube-Client-Name", "5")
|
||||||
.header("X-Goog-Api-Format-Version", "2"),
|
.header("X-Goog-Api-Format-Version", "2"),
|
||||||
};
|
};
|
||||||
r = r.header(header::USER_AGENT, self.user_agent(ctype).as_ref());
|
r = r
|
||||||
|
.header(header::CONTENT_TYPE, "application/json")
|
||||||
|
.header(header::USER_AGENT, self.user_agent(ctype).as_ref());
|
||||||
if let Some(vdata) = self.opts.visitor_data.as_deref().or(visitor_data) {
|
if let Some(vdata) = self.opts.visitor_data.as_deref().or(visitor_data) {
|
||||||
r = r.header("X-Goog-EOM-Visitor-Id", vdata);
|
r = r.header("X-Goog-EOM-Visitor-Id", vdata);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use serde::Serialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
deobfuscate::Deobfuscator,
|
deobfuscate::{DeobfData, Deobfuscator},
|
||||||
error::{internal::DeobfError, Error, ExtractionError, UnavailabilityReason},
|
error::{internal::DeobfError, Error, ExtractionError, UnavailabilityReason},
|
||||||
model::{
|
model::{
|
||||||
traits::QualityOrd, AudioCodec, AudioFormat, AudioStream, AudioTrack, Frameset, Subtitle,
|
traits::QualityOrd, AudioCodec, AudioFormat, AudioStream, AudioTrack, Frameset, Subtitle,
|
||||||
|
|
@ -34,17 +34,12 @@ struct QPlayer<'a> {
|
||||||
/// Website playback context
|
/// Website playback context
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
playback_context: Option<QPlaybackContext<'a>>,
|
playback_context: Option<QPlaybackContext<'a>>,
|
||||||
/// Content playback nonce (mobile only, 16 random chars)
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
cpn: Option<String>,
|
|
||||||
/// YouTube video ID
|
/// YouTube video ID
|
||||||
video_id: &'a str,
|
video_id: &'a str,
|
||||||
/// Set to true to allow extraction of streams with sensitive content
|
/// Set to true to allow extraction of streams with sensitive content
|
||||||
content_check_ok: bool,
|
content_check_ok: bool,
|
||||||
/// Probably refers to allowing sensitive content, too
|
/// Probably refers to allowing sensitive content, too
|
||||||
racy_check_ok: bool,
|
racy_check_ok: bool,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
params: Option<&'a str>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
|
|
@ -125,31 +120,27 @@ impl RustyPipeQuery {
|
||||||
client_type: ClientType,
|
client_type: ClientType,
|
||||||
) -> Result<VideoPlayer, Error> {
|
) -> Result<VideoPlayer, Error> {
|
||||||
let video_id = video_id.as_ref();
|
let video_id = video_id.as_ref();
|
||||||
let deobf = self.client.get_deobf_data().await?;
|
let mut deobf = None;
|
||||||
|
|
||||||
let request_body = if client_type.is_web() {
|
let request_body = if client_type.needs_deobf() {
|
||||||
|
deobf = Some(self.client.get_deobf_data().await?);
|
||||||
QPlayer {
|
QPlayer {
|
||||||
playback_context: Some(QPlaybackContext {
|
playback_context: Some(QPlaybackContext {
|
||||||
content_playback_context: QContentPlaybackContext {
|
content_playback_context: QContentPlaybackContext {
|
||||||
signature_timestamp: &deobf.sts,
|
signature_timestamp: &deobf.as_ref().unwrap().sts,
|
||||||
referer: format!("https://www.youtube.com/watch?v={video_id}"),
|
referer: format!("https://www.youtube.com/watch?v={video_id}"),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
cpn: None,
|
|
||||||
video_id,
|
video_id,
|
||||||
content_check_ok: true,
|
content_check_ok: true,
|
||||||
racy_check_ok: true,
|
racy_check_ok: true,
|
||||||
params: None,
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
QPlayer {
|
QPlayer {
|
||||||
playback_context: None,
|
playback_context: None,
|
||||||
cpn: Some(util::generate_content_playback_nonce()),
|
|
||||||
video_id,
|
video_id,
|
||||||
content_check_ok: true,
|
content_check_ok: true,
|
||||||
racy_check_ok: true,
|
racy_check_ok: true,
|
||||||
// Source: https://github.com/TeamNewPipe/NewPipeExtractor/pull/1168
|
|
||||||
params: Some("CgIIAQ%3D%3D").filter(|_| client_type == ClientType::Android),
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -160,7 +151,7 @@ impl RustyPipeQuery {
|
||||||
"player",
|
"player",
|
||||||
&request_body,
|
&request_body,
|
||||||
MapRespOptions {
|
MapRespOptions {
|
||||||
deobf: Some(&deobf),
|
deobf: deobf.as_ref(),
|
||||||
unlocalized: true,
|
unlocalized: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|
@ -277,7 +268,7 @@ impl MapResponse<VideoPlayer> for response::Player {
|
||||||
};
|
};
|
||||||
|
|
||||||
let streams = if !is_live {
|
let streams = if !is_live {
|
||||||
let mut mapper = StreamsMapper::new(Deobfuscator::new(ctx.deobf.unwrap())?);
|
let mut mapper = StreamsMapper::new(ctx.deobf)?;
|
||||||
mapper.map_streams(streaming_data.formats);
|
mapper.map_streams(streaming_data.formats);
|
||||||
mapper.map_streams(streaming_data.adaptive_formats);
|
mapper.map_streams(streaming_data.adaptive_formats);
|
||||||
let mut res = mapper.output()?;
|
let mut res = mapper.output()?;
|
||||||
|
|
@ -374,7 +365,7 @@ impl MapResponse<VideoPlayer> for response::Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StreamsMapper {
|
struct StreamsMapper {
|
||||||
deobf: Deobfuscator,
|
deobf: Option<Deobfuscator>,
|
||||||
streams: Streams,
|
streams: Streams,
|
||||||
warnings: Vec<String>,
|
warnings: Vec<String>,
|
||||||
/// First stream mapping error
|
/// First stream mapping error
|
||||||
|
|
@ -393,15 +384,20 @@ struct Streams {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StreamsMapper {
|
impl StreamsMapper {
|
||||||
fn new(deobf: Deobfuscator) -> Self {
|
fn new(deobf_data: Option<&DeobfData>) -> Result<Self, DeobfError> {
|
||||||
Self {
|
let deobf = match deobf_data {
|
||||||
|
Some(deobf_data) => Some(Deobfuscator::new(deobf_data)?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
deobf,
|
deobf,
|
||||||
streams: Streams::default(),
|
streams: Streams::default(),
|
||||||
warnings: Vec::new(),
|
warnings: Vec::new(),
|
||||||
first_err: None,
|
first_err: None,
|
||||||
last_nsig: String::new(),
|
last_nsig: String::new(),
|
||||||
last_nsig_deobf: String::new(),
|
last_nsig_deobf: String::new(),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_streams(&mut self, mut streams: MapResult<Vec<Format>>) {
|
fn map_streams(&mut self, mut streams: MapResult<Vec<Format>>) {
|
||||||
|
|
@ -461,6 +457,12 @@ impl StreamsMapper {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deobf(&self) -> Result<&Deobfuscator, DeobfError> {
|
||||||
|
self.deobf
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(DeobfError::Other("no deobfuscator"))
|
||||||
|
}
|
||||||
|
|
||||||
fn cipher_to_url_params(
|
fn cipher_to_url_params(
|
||||||
&self,
|
&self,
|
||||||
signature_cipher: &str,
|
signature_cipher: &str,
|
||||||
|
|
@ -481,7 +483,7 @@ impl StreamsMapper {
|
||||||
let (url_base, mut url_params) =
|
let (url_base, mut url_params) =
|
||||||
util::url_to_params(raw_url).or(Err(DeobfError::Extraction("url params")))?;
|
util::url_to_params(raw_url).or(Err(DeobfError::Extraction("url params")))?;
|
||||||
|
|
||||||
let deobf_sig = self.deobf.deobfuscate_sig(sig)?;
|
let deobf_sig = self.deobf()?.deobfuscate_sig(sig)?;
|
||||||
url_params.insert(sp.to_string(), deobf_sig);
|
url_params.insert(sp.to_string(), deobf_sig);
|
||||||
|
|
||||||
Ok((url_base, url_params))
|
Ok((url_base, url_params))
|
||||||
|
|
@ -492,7 +494,7 @@ impl StreamsMapper {
|
||||||
let nsig = if n == &self.last_nsig {
|
let nsig = if n == &self.last_nsig {
|
||||||
self.last_nsig_deobf.to_owned()
|
self.last_nsig_deobf.to_owned()
|
||||||
} else {
|
} else {
|
||||||
let nsig = self.deobf.deobfuscate_nsig(n)?;
|
let nsig = self.deobf()?.deobfuscate_nsig(n)?;
|
||||||
self.last_nsig.clone_from(n);
|
self.last_nsig.clone_from(n);
|
||||||
self.last_nsig_deobf.clone_from(&nsig);
|
self.last_nsig_deobf.clone_from(&nsig);
|
||||||
nsig
|
nsig
|
||||||
|
|
@ -782,7 +784,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn cipher_to_url() {
|
fn cipher_to_url() {
|
||||||
let signature_cipher = "s=w%3DAe%3DA6aDNQLkViKS7LOm9QtxZJHKwb53riq9qEFw-ecBWJCAiA%3DcEg0tn3dty9jEHszfzh4Ud__bg9CEHVx4ix-7dKsIPAhIQRw8JQ0qOA&sp=sig&url=https://rr5---sn-h0jelnez.googlevideo.com/videoplayback%3Fexpire%3D1659376413%26ei%3Dvb7nYvH5BMK8gAfBj7ToBQ%26ip%3D2003%253Ade%253Aaf06%253A6300%253Ac750%253A1b77%253Ac74a%253A80e3%26id%3Do-AB_BABwrXZJN428ZwDxq5ScPn2AbcGODnRlTVhCQ3mj2%26itag%3D251%26source%3Dyoutube%26requiressl%3Dyes%26mh%3DhH%26mm%3D31%252C26%26mn%3Dsn-h0jelnez%252Csn-4g5ednsl%26ms%3Dau%252Conr%26mv%3Dm%26mvi%3D5%26pl%3D37%26initcwndbps%3D1588750%26spc%3DlT-Khi831z8dTejFIRCvCEwx_6romtM%26vprv%3D1%26mime%3Daudio%252Fwebm%26ns%3Db_Mq_qlTFcSGlG9RpwpM9xQH%26gir%3Dyes%26clen%3D3781277%26dur%3D229.301%26lmt%3D1655510291473933%26mt%3D1659354538%26fvip%3D5%26keepalive%3Dyes%26fexp%3D24001373%252C24007246%26c%3DWEB%26rbqsm%3Dfr%26txp%3D4532434%26n%3Dd2g6G2hVqWIXxedQ%26sparams%3Dexpire%252Cei%252Cip%252Cid%252Citag%252Csource%252Crequiressl%252Cspc%252Cvprv%252Cmime%252Cns%252Cgir%252Cclen%252Cdur%252Clmt%26lsparams%3Dmh%252Cmm%252Cmn%252Cms%252Cmv%252Cmvi%252Cpl%252Cinitcwndbps%26lsig%3DAG3C_xAwRQIgCKCGJ1iu4wlaGXy3jcJyU3inh9dr1FIfqYOZEG_MdmACIQCbungkQYFk7EhD6K2YvLaHFMjKOFWjw001_tLb0lPDtg%253D%253D";
|
let signature_cipher = "s=w%3DAe%3DA6aDNQLkViKS7LOm9QtxZJHKwb53riq9qEFw-ecBWJCAiA%3DcEg0tn3dty9jEHszfzh4Ud__bg9CEHVx4ix-7dKsIPAhIQRw8JQ0qOA&sp=sig&url=https://rr5---sn-h0jelnez.googlevideo.com/videoplayback%3Fexpire%3D1659376413%26ei%3Dvb7nYvH5BMK8gAfBj7ToBQ%26ip%3D2003%253Ade%253Aaf06%253A6300%253Ac750%253A1b77%253Ac74a%253A80e3%26id%3Do-AB_BABwrXZJN428ZwDxq5ScPn2AbcGODnRlTVhCQ3mj2%26itag%3D251%26source%3Dyoutube%26requiressl%3Dyes%26mh%3DhH%26mm%3D31%252C26%26mn%3Dsn-h0jelnez%252Csn-4g5ednsl%26ms%3Dau%252Conr%26mv%3Dm%26mvi%3D5%26pl%3D37%26initcwndbps%3D1588750%26spc%3DlT-Khi831z8dTejFIRCvCEwx_6romtM%26vprv%3D1%26mime%3Daudio%252Fwebm%26ns%3Db_Mq_qlTFcSGlG9RpwpM9xQH%26gir%3Dyes%26clen%3D3781277%26dur%3D229.301%26lmt%3D1655510291473933%26mt%3D1659354538%26fvip%3D5%26keepalive%3Dyes%26fexp%3D24001373%252C24007246%26c%3DWEB%26rbqsm%3Dfr%26txp%3D4532434%26n%3Dd2g6G2hVqWIXxedQ%26sparams%3Dexpire%252Cei%252Cip%252Cid%252Citag%252Csource%252Crequiressl%252Cspc%252Cvprv%252Cmime%252Cns%252Cgir%252Cclen%252Cdur%252Clmt%26lsparams%3Dmh%252Cmm%252Cmn%252Cms%252Cmv%252Cmvi%252Cpl%252Cinitcwndbps%26lsig%3DAG3C_xAwRQIgCKCGJ1iu4wlaGXy3jcJyU3inh9dr1FIfqYOZEG_MdmACIQCbungkQYFk7EhD6K2YvLaHFMjKOFWjw001_tLb0lPDtg%253D%253D";
|
||||||
let mut mapper = StreamsMapper::new(Deobfuscator::new(&DEOBF_DATA).unwrap());
|
let mut mapper = StreamsMapper::new(Some(&DEOBF_DATA)).unwrap();
|
||||||
let url = mapper
|
let url = mapper
|
||||||
.map_url(&None, &Some(signature_cipher.to_owned()))
|
.map_url(&None, &Some(signature_cipher.to_owned()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
||||||
Reference in a new issue