fix: add pedantic lints
This commit is contained in:
parent
81280200f7
commit
cbeb14f3fd
41 changed files with 520 additions and 447 deletions
|
|
@ -322,7 +322,7 @@ fn map_vanity_url(url: &str, id: &str) -> Option<String> {
|
|||
|
||||
Url::parse(url).ok().map(|mut parsed_url| {
|
||||
// The vanity URL from YouTube is http for some reason
|
||||
let _ = parsed_url.set_scheme("https");
|
||||
_ = parsed_url.set_scheme("https");
|
||||
parsed_url.to_string()
|
||||
})
|
||||
}
|
||||
|
|
@ -392,11 +392,8 @@ fn map_channel(
|
|||
content: (),
|
||||
},
|
||||
response::channel::Header::CarouselHeaderRenderer(carousel) => {
|
||||
let hdata = carousel
|
||||
.contents
|
||||
.into_iter()
|
||||
.filter_map(|item| {
|
||||
match item {
|
||||
let hdata = carousel.contents.into_iter().find_map(|item| {
|
||||
match item {
|
||||
response::channel::CarouselHeaderRendererItem::TopicChannelDetailsRenderer {
|
||||
subscriber_count_text,
|
||||
subtitle,
|
||||
|
|
@ -404,8 +401,7 @@ fn map_channel(
|
|||
} => Some((subscriber_count_text.or(subtitle), avatar)),
|
||||
response::channel::CarouselHeaderRendererItem::None => None,
|
||||
}
|
||||
})
|
||||
.next();
|
||||
});
|
||||
|
||||
Channel {
|
||||
id: metadata.external_id,
|
||||
|
|
@ -568,7 +564,7 @@ fn _order_ctoken(
|
|||
pb_80226972.string(3, &pbi.to_base64());
|
||||
|
||||
let mut pb = ProtoBuilder::new();
|
||||
pb.embedded(80226972, pb_80226972);
|
||||
pb.embedded(80_226_972, pb_80226972);
|
||||
|
||||
pb.to_base64()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::collections::BTreeMap;
|
|||
use crate::{
|
||||
error::{Error, ExtractionError},
|
||||
model::ChannelRss,
|
||||
report::Report,
|
||||
report::{Report, RustyPipeInfo},
|
||||
};
|
||||
|
||||
use super::{response, RustyPipeQuery};
|
||||
|
|
@ -19,10 +19,7 @@ impl RustyPipeQuery {
|
|||
/// The downside of using the RSS feed is that it does not provide video durations.
|
||||
pub async fn channel_rss<S: AsRef<str>>(&self, channel_id: S) -> Result<ChannelRss, Error> {
|
||||
let channel_id = channel_id.as_ref();
|
||||
let url = format!(
|
||||
"https://www.youtube.com/feeds/videos.xml?channel_id={}",
|
||||
channel_id,
|
||||
);
|
||||
let url = format!("https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}");
|
||||
let xml = self
|
||||
.client
|
||||
.http_request_txt(&self.client.inner.http.get(&url).build()?)
|
||||
|
|
@ -40,15 +37,15 @@ impl RustyPipeQuery {
|
|||
Err(e) => {
|
||||
if let Some(reporter) = &self.client.inner.reporter {
|
||||
let report = Report {
|
||||
info: Default::default(),
|
||||
info: RustyPipeInfo::default(),
|
||||
level: crate::report::Level::ERR,
|
||||
operation: "channel_rss".to_owned(),
|
||||
operation: "channel_rss",
|
||||
error: Some(e.to_string()),
|
||||
msgs: Vec::new(),
|
||||
deobf_data: None,
|
||||
http_request: crate::report::HTTPRequest {
|
||||
url,
|
||||
method: "GET".to_owned(),
|
||||
url: &url,
|
||||
method: "GET",
|
||||
req_header: BTreeMap::new(),
|
||||
req_body: String::new(),
|
||||
status: 200,
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ use crate::{
|
|||
deobfuscate::DeobfData,
|
||||
error::{Error, ExtractionError},
|
||||
param::{Country, Language},
|
||||
report::{FileReporter, Level, Report, Reporter, DEFAULT_REPORT_DIR},
|
||||
report::{FileReporter, Level, Report, Reporter, RustyPipeInfo, DEFAULT_REPORT_DIR},
|
||||
serializer::MapResult,
|
||||
util,
|
||||
};
|
||||
|
|
@ -73,7 +73,7 @@ pub enum ClientType {
|
|||
}
|
||||
|
||||
impl ClientType {
|
||||
fn is_web(&self) -> bool {
|
||||
fn is_web(self) -> bool {
|
||||
match self {
|
||||
ClientType::Desktop | ClientType::DesktopMusic | ClientType::TvHtml5Embed => true,
|
||||
ClientType::Android | ClientType::Ios => false,
|
||||
|
|
@ -118,11 +118,11 @@ struct ClientInfo<'a> {
|
|||
impl Default for ClientInfo<'_> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
client_name: Default::default(),
|
||||
client_version: Default::default(),
|
||||
client_name: "",
|
||||
client_version: Cow::default(),
|
||||
client_screen: None,
|
||||
device_model: None,
|
||||
platform: Default::default(),
|
||||
platform: "",
|
||||
original_url: None,
|
||||
visitor_data: None,
|
||||
hl: Language::En,
|
||||
|
|
@ -432,6 +432,7 @@ impl RustyPipeBuilder {
|
|||
/// Return a new `RustyPipeBuilder`.
|
||||
///
|
||||
/// This is the same as [`RustyPipe::builder`]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
RustyPipeBuilder {
|
||||
default_opts: RustyPipeOpts::default(),
|
||||
|
|
@ -445,6 +446,7 @@ impl RustyPipeBuilder {
|
|||
}
|
||||
|
||||
/// Return a new, configured RustyPipe instance.
|
||||
#[must_use]
|
||||
pub fn build(self) -> RustyPipe {
|
||||
let mut client_builder = ClientBuilder::new()
|
||||
.user_agent(self.user_agent.unwrap_or_else(|| DEFAULT_UA.to_owned()))
|
||||
|
|
@ -509,6 +511,7 @@ impl RustyPipeBuilder {
|
|||
/// This option has no effect if the storage backend or reporter are manually set or disabled.
|
||||
///
|
||||
/// **Default value**: current working directory
|
||||
#[must_use]
|
||||
pub fn storage_dir<P: Into<PathBuf>>(mut self, path: P) -> Self {
|
||||
self.storage_dir = Some(path.into());
|
||||
self
|
||||
|
|
@ -519,12 +522,14 @@ impl RustyPipeBuilder {
|
|||
/// program executions.
|
||||
///
|
||||
/// **Default value**: [`FileStorage`] in `rustypipe_cache.json`
|
||||
#[must_use]
|
||||
pub fn storage(mut self, storage: Box<dyn CacheStorage>) -> Self {
|
||||
self.storage = DefaultOpt::Some(storage);
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable cache storage
|
||||
#[must_use]
|
||||
pub fn no_storage(mut self) -> Self {
|
||||
self.storage = DefaultOpt::None;
|
||||
self
|
||||
|
|
@ -533,12 +538,14 @@ impl RustyPipeBuilder {
|
|||
/// Add a `Reporter` to collect error details
|
||||
///
|
||||
/// **Default value**: [`FileReporter`] creating reports in `./rustypipe_reports`
|
||||
#[must_use]
|
||||
pub fn reporter(mut self, reporter: Box<dyn Reporter>) -> Self {
|
||||
self.reporter = DefaultOpt::Some(reporter);
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable the creation of report files in case of errors and warnings.
|
||||
#[must_use]
|
||||
pub fn no_reporter(mut self) -> Self {
|
||||
self.reporter = DefaultOpt::None;
|
||||
self
|
||||
|
|
@ -550,12 +557,14 @@ impl RustyPipeBuilder {
|
|||
/// response body has finished.
|
||||
///
|
||||
/// **Default value**: 10s
|
||||
#[must_use]
|
||||
pub fn timeout(mut self, timeout: Duration) -> Self {
|
||||
self.timeout = DefaultOpt::Some(timeout);
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable the HTTP request timeout.
|
||||
#[must_use]
|
||||
pub fn no_timeout(mut self) -> Self {
|
||||
self.timeout = DefaultOpt::None;
|
||||
self
|
||||
|
|
@ -570,6 +579,7 @@ impl RustyPipeBuilder {
|
|||
/// random jitter to be less predictable).
|
||||
///
|
||||
/// **Default value**: 2
|
||||
#[must_use]
|
||||
pub fn n_http_retries(mut self, n_retries: u32) -> Self {
|
||||
self.n_http_retries = n_retries;
|
||||
self
|
||||
|
|
@ -579,6 +589,7 @@ impl RustyPipeBuilder {
|
|||
///
|
||||
/// **Default value**: `Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0`
|
||||
/// (Firefox ESR on Debian)
|
||||
#[must_use]
|
||||
pub fn user_agent<S: Into<String>>(mut self, user_agent: S) -> Self {
|
||||
self.user_agent = Some(user_agent.into());
|
||||
self
|
||||
|
|
@ -591,6 +602,7 @@ impl RustyPipeBuilder {
|
|||
/// **Default value**: `Language::En` (English)
|
||||
///
|
||||
/// **Info**: you can set this option for individual queries, too
|
||||
#[must_use]
|
||||
pub fn lang(mut self, lang: Language) -> Self {
|
||||
self.default_opts.lang = lang;
|
||||
self
|
||||
|
|
@ -603,6 +615,7 @@ impl RustyPipeBuilder {
|
|||
/// **Default value**: `Country::Us` (USA)
|
||||
///
|
||||
/// **Info**: you can set this option for individual queries, too
|
||||
#[must_use]
|
||||
pub fn country(mut self, country: Country) -> Self {
|
||||
self.default_opts.country = validate_country(country);
|
||||
self
|
||||
|
|
@ -613,6 +626,7 @@ impl RustyPipeBuilder {
|
|||
/// This should only be used for debugging.
|
||||
///
|
||||
/// **Info**: you can set this option for individual queries, too
|
||||
#[must_use]
|
||||
pub fn report(mut self) -> Self {
|
||||
self.default_opts.report = true;
|
||||
self
|
||||
|
|
@ -624,6 +638,7 @@ impl RustyPipeBuilder {
|
|||
/// This should only be used for testing.
|
||||
///
|
||||
/// **Info**: you can set this option for individual queries, too
|
||||
#[must_use]
|
||||
pub fn strict(mut self) -> Self {
|
||||
self.default_opts.strict = true;
|
||||
self
|
||||
|
|
@ -643,6 +658,7 @@ impl RustyPipeBuilder {
|
|||
/// visitor, so you should not use the same vistor data cookie for batch operations.
|
||||
///
|
||||
/// **Info**: you can set this option for individual queries, too
|
||||
#[must_use]
|
||||
pub fn visitor_data<S: Into<String>>(mut self, visitor_data: S) -> Self {
|
||||
self.default_opts.visitor_data = Some(visitor_data.into());
|
||||
self
|
||||
|
|
@ -653,6 +669,7 @@ impl RustyPipeBuilder {
|
|||
/// see also [`RustyPipeBuilder::visitor_data`]
|
||||
///
|
||||
/// **Info**: you can set this option for individual queries, too
|
||||
#[must_use]
|
||||
pub fn visitor_data_opt<S: Into<String>>(mut self, visitor_data: Option<S>) -> Self {
|
||||
self.default_opts.visitor_data = visitor_data.map(S::into);
|
||||
self
|
||||
|
|
@ -669,6 +686,7 @@ impl RustyPipe {
|
|||
/// Create a new RustyPipe instance with default settings.
|
||||
///
|
||||
/// To create an instance with custom options, use [`RustyPipeBuilder`] instead.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
RustyPipeBuilder::new().build()
|
||||
}
|
||||
|
|
@ -676,11 +694,13 @@ impl RustyPipe {
|
|||
/// Create a new [`RustyPipeBuilder`]
|
||||
///
|
||||
/// This is the same as [`RustyPipeBuilder::new`]
|
||||
#[must_use]
|
||||
pub fn builder() -> RustyPipeBuilder {
|
||||
RustyPipeBuilder::new()
|
||||
}
|
||||
|
||||
/// Create a new [`RustyPipeQuery`] to run an API request
|
||||
#[must_use]
|
||||
pub fn query(&self) -> RustyPipeQuery {
|
||||
RustyPipeQuery {
|
||||
client: self.clone(),
|
||||
|
|
@ -779,7 +799,7 @@ impl RustyPipe {
|
|||
.get(sw_url)
|
||||
.header(header::ORIGIN, origin)
|
||||
.header(header::REFERER, origin)
|
||||
.header(header::COOKIE, self.inner.consent_cookie.to_owned())
|
||||
.header(header::COOKIE, self.inner.consent_cookie.clone())
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
|
|
@ -828,13 +848,13 @@ impl RustyPipe {
|
|||
let mut desktop_client = self.inner.cache.desktop_client.write().await;
|
||||
|
||||
match desktop_client.get() {
|
||||
Some(cdata) => cdata.version.to_owned(),
|
||||
Some(cdata) => cdata.version.clone(),
|
||||
None => {
|
||||
log::debug!("getting desktop client version");
|
||||
match self.extract_desktop_client_version().await {
|
||||
Ok(version) => {
|
||||
*desktop_client = CacheEntry::from(ClientData {
|
||||
version: version.to_owned(),
|
||||
version: version.clone(),
|
||||
});
|
||||
drop(desktop_client);
|
||||
self.store_cache().await;
|
||||
|
|
@ -860,13 +880,13 @@ impl RustyPipe {
|
|||
let mut music_client = self.inner.cache.music_client.write().await;
|
||||
|
||||
match music_client.get() {
|
||||
Some(cdata) => cdata.version.to_owned(),
|
||||
Some(cdata) => cdata.version.clone(),
|
||||
None => {
|
||||
log::debug!("getting music client version");
|
||||
match self.extract_music_client_version().await {
|
||||
Ok(version) => {
|
||||
*music_client = CacheEntry::from(ClientData {
|
||||
version: version.to_owned(),
|
||||
version: version.clone(),
|
||||
});
|
||||
drop(music_client);
|
||||
self.store_cache().await;
|
||||
|
|
@ -944,6 +964,7 @@ impl RustyPipeQuery {
|
|||
/// Set the language parameter used when accessing the YouTube API
|
||||
///
|
||||
/// This will change multilanguage video titles, descriptions and textual dates
|
||||
#[must_use]
|
||||
pub fn lang(mut self, lang: Language) -> Self {
|
||||
self.opts.lang = lang;
|
||||
self
|
||||
|
|
@ -952,6 +973,7 @@ impl RustyPipeQuery {
|
|||
/// Set the country parameter used when accessing the YouTube API.
|
||||
///
|
||||
/// This will change trends and recommended content.
|
||||
#[must_use]
|
||||
pub fn country(mut self, country: Country) -> Self {
|
||||
self.opts.country = validate_country(country);
|
||||
self
|
||||
|
|
@ -960,6 +982,7 @@ impl RustyPipeQuery {
|
|||
/// Generate a report on every operation.
|
||||
///
|
||||
/// This should only be used for debugging.
|
||||
#[must_use]
|
||||
pub fn report(mut self) -> Self {
|
||||
self.opts.report = true;
|
||||
self
|
||||
|
|
@ -969,6 +992,7 @@ impl RustyPipeQuery {
|
|||
/// are warnings during deserialization (e.g. invalid items).
|
||||
///
|
||||
/// This should only be used for testing.
|
||||
#[must_use]
|
||||
pub fn strict(mut self) -> Self {
|
||||
self.opts.strict = true;
|
||||
self
|
||||
|
|
@ -986,6 +1010,7 @@ impl RustyPipeQuery {
|
|||
///
|
||||
/// Note that YouTube has a rate limit on the number of requests from a single
|
||||
/// visitor, so you should not use the same vistor data cookie for batch operations.
|
||||
#[must_use]
|
||||
pub fn visitor_data<S: Into<String>>(mut self, visitor_data: S) -> Self {
|
||||
self.opts.visitor_data = Some(visitor_data.into());
|
||||
self
|
||||
|
|
@ -994,6 +1019,7 @@ impl RustyPipeQuery {
|
|||
/// Set the YouTube visitor data cookie to an optional value
|
||||
///
|
||||
/// see also [`RustyPipeQuery::visitor_data`]
|
||||
#[must_use]
|
||||
pub fn visitor_data_opt<S: Into<String>>(mut self, visitor_data: Option<S>) -> Self {
|
||||
self.opts.visitor_data = visitor_data.map(S::into);
|
||||
self
|
||||
|
|
@ -1011,13 +1037,10 @@ impl RustyPipeQuery {
|
|||
localized: bool,
|
||||
visitor_data: Option<&'a str>,
|
||||
) -> YTContext {
|
||||
let hl = match localized {
|
||||
true => self.opts.lang,
|
||||
false => Language::En,
|
||||
};
|
||||
let gl = match localized {
|
||||
true => self.opts.country,
|
||||
false => Country::Us,
|
||||
let (hl, gl) = if localized {
|
||||
(self.opts.lang, self.opts.country)
|
||||
} else {
|
||||
(Language::En, Country::Us)
|
||||
};
|
||||
let visitor_data = self.opts.visitor_data.as_deref().or(visitor_data);
|
||||
|
||||
|
|
@ -1119,7 +1142,7 @@ impl RustyPipeQuery {
|
|||
))
|
||||
.header(header::ORIGIN, YOUTUBE_HOME_URL)
|
||||
.header(header::REFERER, YOUTUBE_HOME_URL)
|
||||
.header(header::COOKIE, self.client.inner.consent_cookie.to_owned())
|
||||
.header(header::COOKIE, self.client.inner.consent_cookie.clone())
|
||||
.header("X-YouTube-Client-Name", "1")
|
||||
.header(
|
||||
"X-YouTube-Client-Version",
|
||||
|
|
@ -1134,7 +1157,7 @@ impl RustyPipeQuery {
|
|||
))
|
||||
.header(header::ORIGIN, YOUTUBE_MUSIC_HOME_URL)
|
||||
.header(header::REFERER, YOUTUBE_MUSIC_HOME_URL)
|
||||
.header(header::COOKIE, self.client.inner.consent_cookie.to_owned())
|
||||
.header(header::COOKIE, self.client.inner.consent_cookie.clone())
|
||||
.header("X-YouTube-Client-Name", "67")
|
||||
.header(
|
||||
"X-YouTube-Client-Version",
|
||||
|
|
@ -1187,7 +1210,7 @@ impl RustyPipeQuery {
|
|||
/// Get a YouTube visitor data cookie, which is necessary for certain requests
|
||||
async fn get_visitor_data(&self) -> Result<String, Error> {
|
||||
match &self.opts.visitor_data {
|
||||
Some(vd) => Ok(vd.to_owned()),
|
||||
Some(vd) => Ok(vd.clone()),
|
||||
None => self.client.get_visitor_data().await,
|
||||
}
|
||||
}
|
||||
|
|
@ -1333,21 +1356,19 @@ impl RustyPipeQuery {
|
|||
if level > Level::DBG || self.opts.report {
|
||||
if let Some(reporter) = &self.client.inner.reporter {
|
||||
let report = Report {
|
||||
info: Default::default(),
|
||||
info: RustyPipeInfo::default(),
|
||||
level,
|
||||
operation: format!("{operation}({id})"),
|
||||
operation: &format!("{operation}({id})"),
|
||||
error,
|
||||
msgs,
|
||||
deobf_data: deobf.cloned(),
|
||||
http_request: crate::report::HTTPRequest {
|
||||
url: request.url().to_string(),
|
||||
method: "POST".to_string(),
|
||||
url: request.url().as_str(),
|
||||
method: request.method().as_str(),
|
||||
req_header: request
|
||||
.headers()
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
(k.to_string(), v.to_str().unwrap_or_default().to_owned())
|
||||
})
|
||||
.map(|(k, v)| (k.as_str(), v.to_str().unwrap_or_default().to_owned()))
|
||||
.collect(),
|
||||
req_body: serde_json::to_string(body).unwrap_or_default(),
|
||||
status: req_res.status.into(),
|
||||
|
|
|
|||
|
|
@ -26,9 +26,10 @@ impl RustyPipeQuery {
|
|||
all_albums: bool,
|
||||
) -> Result<MusicArtist, Error> {
|
||||
let artist_id = artist_id.as_ref();
|
||||
let visitor_data = match all_albums {
|
||||
true => Some(self.get_visitor_data().await?),
|
||||
false => None,
|
||||
let visitor_data = if all_albums {
|
||||
Some(self.get_visitor_data().await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let res = self._music_artist(artist_id, visitor_data.as_deref()).await;
|
||||
|
|
@ -196,7 +197,7 @@ fn map_artist_page(
|
|||
lang,
|
||||
ArtistId {
|
||||
id: Some(id.to_owned()),
|
||||
name: header.title.to_owned(),
|
||||
name: header.title.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ impl RustyPipeQuery {
|
|||
// In rare cases, albums may have track numbers =0 (example: MPREb_RM0QfZ0eSKL)
|
||||
// They should be replaced with the track number derived from the previous track.
|
||||
let mut n_prev = 0;
|
||||
for track in album.tracks.iter_mut() {
|
||||
for track in &mut album.tracks {
|
||||
let tn = track.track_nr.unwrap_or_default();
|
||||
if tn == 0 {
|
||||
n_prev += 1;
|
||||
|
|
@ -80,7 +80,7 @@ impl RustyPipeQuery {
|
|||
.enumerate()
|
||||
.filter_map(|(i, track)| {
|
||||
if track.is_video {
|
||||
Some((i, track.name.to_owned()))
|
||||
Some((i, track.name.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ impl RustyPipeQuery {
|
|||
for (i, title) in to_replace {
|
||||
let found_track = playlist.tracks.items.iter().find_map(|track| {
|
||||
if track.name == title && !track.is_video {
|
||||
Some((track.id.to_owned(), track.duration))
|
||||
Some((track.id.clone(), track.duration))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -173,7 +173,7 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
|
|||
.split(|p| p == DOT_SEPARATOR)
|
||||
.collect::<Vec<_>>();
|
||||
parts
|
||||
.get(if parts.len() > 2 { 1 } else { 0 })
|
||||
.get(usize::from(parts.len() > 2))
|
||||
.and_then(|txt| util::parse_numeric::<u64>(&txt[0]).ok())
|
||||
})
|
||||
} else {
|
||||
|
|
@ -293,7 +293,7 @@ impl MapResponse<MusicAlbum> for response::MusicPlaylist {
|
|||
match section {
|
||||
response::music_item::ItemSection::MusicShelfRenderer(sh) => shelf = Some(sh),
|
||||
response::music_item::ItemSection::MusicCarouselShelfRenderer(sh) => {
|
||||
album_variants = Some(sh.contents)
|
||||
album_variants = Some(sh.contents);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
|
@ -355,7 +355,7 @@ impl MapResponse<MusicAlbum> for response::MusicPlaylist {
|
|||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let artist_id = artist_id.or_else(|| artists.first().and_then(|a| a.id.to_owned()));
|
||||
let artist_id = artist_id.or_else(|| artists.first().and_then(|a| a.id.clone()));
|
||||
|
||||
let mut mapper = MusicListMapper::with_album(
|
||||
lang,
|
||||
|
|
@ -363,7 +363,7 @@ impl MapResponse<MusicAlbum> for response::MusicPlaylist {
|
|||
by_va,
|
||||
AlbumId {
|
||||
id: id.to_owned(),
|
||||
name: header.title.to_owned(),
|
||||
name: header.title.clone(),
|
||||
},
|
||||
);
|
||||
mapper.map_response(shelf.contents);
|
||||
|
|
|
|||
|
|
@ -170,9 +170,10 @@ impl RustyPipeQuery {
|
|||
) -> Result<MusicSearchFiltered<MusicPlaylistItem>, Error> {
|
||||
self._music_search_playlists(
|
||||
query,
|
||||
match community {
|
||||
true => Params::CommunityPlaylists,
|
||||
false => Params::YtmPlaylists,
|
||||
if community {
|
||||
Params::CommunityPlaylists
|
||||
} else {
|
||||
Params::YtmPlaylists
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
|
@ -266,7 +267,7 @@ impl MapResponse<MusicSearchResult> for response::MusicSearch {
|
|||
}
|
||||
response::music_search::ItemSection::ItemSectionRenderer { contents } => {
|
||||
if let Some(corrected) = contents.into_iter().next() {
|
||||
corrected_query = Some(corrected.showing_results_for_renderer.corrected_query)
|
||||
corrected_query = Some(corrected.showing_results_for_renderer.corrected_query);
|
||||
}
|
||||
}
|
||||
response::music_search::ItemSection::None => {}
|
||||
|
|
@ -324,7 +325,7 @@ impl<T: FromYtItem> MapResponse<MusicSearchFiltered<T>> for response::MusicSearc
|
|||
}
|
||||
response::music_search::ItemSection::ItemSectionRenderer { contents } => {
|
||||
if let Some(corrected) = contents.into_iter().next() {
|
||||
corrected_query = Some(corrected.showing_results_for_renderer.corrected_query)
|
||||
corrected_query = Some(corrected.showing_results_for_renderer.corrected_query);
|
||||
}
|
||||
}
|
||||
response::music_search::ItemSection::None => {}
|
||||
|
|
|
|||
|
|
@ -177,12 +177,12 @@ impl MapResponse<VideoPlayer> for response::Player {
|
|||
}
|
||||
response::player::PlayabilityStatus::LoginRequired { reason, messages } => {
|
||||
let mut msg = reason;
|
||||
messages.iter().for_each(|m| {
|
||||
for m in &messages {
|
||||
if !msg.is_empty() {
|
||||
msg.push(' ');
|
||||
}
|
||||
msg.push_str(m);
|
||||
});
|
||||
}
|
||||
|
||||
// reason (age restriction): "Sign in to confirm your age"
|
||||
// or: "This video may be inappropriate for some users."
|
||||
|
|
@ -341,9 +341,9 @@ impl MapResponse<VideoPlayer> for response::Player {
|
|||
+ "&sigh="
|
||||
+ sigh;
|
||||
|
||||
let sprite_count = ((total_count as f64)
|
||||
/ (frames_per_page_x * frames_per_page_y) as f64)
|
||||
.ceil() as u32;
|
||||
let sprite_count = (f64::from(total_count)
|
||||
/ f64::from(frames_per_page_x * frames_per_page_y))
|
||||
.ceil() as u32;
|
||||
|
||||
Some(Frameset {
|
||||
url_template: url,
|
||||
|
|
@ -413,11 +413,11 @@ fn deobf_nsig(
|
|||
let nsig: String;
|
||||
if let Some(n) = url_params.get("n") {
|
||||
nsig = if n == &last_nsig[0] {
|
||||
last_nsig[1].to_owned()
|
||||
last_nsig[1].clone()
|
||||
} else {
|
||||
let nsig = deobf.deobfuscate_nsig(n)?;
|
||||
last_nsig[0] = n.to_string();
|
||||
last_nsig[1] = nsig.to_owned();
|
||||
last_nsig[1] = nsig.clone();
|
||||
nsig
|
||||
};
|
||||
|
||||
|
|
@ -490,25 +490,19 @@ fn map_video_stream(
|
|||
deobf: &Deobfuscator,
|
||||
last_nsig: &mut [String; 2],
|
||||
) -> MapResult<Option<VideoStream>> {
|
||||
let (mtype, codecs) = match parse_mime(&f.mime_type) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return MapResult {
|
||||
c: None,
|
||||
warnings: vec![format!(
|
||||
"Invalid mime type `{}` in video format {:?}",
|
||||
&f.mime_type, &f
|
||||
)],
|
||||
}
|
||||
let Some((mtype, codecs)) = parse_mime(&f.mime_type) else {
|
||||
return MapResult {
|
||||
c: None,
|
||||
warnings: vec![format!(
|
||||
"Invalid mime type `{}` in video format {:?}",
|
||||
&f.mime_type, &f
|
||||
)],
|
||||
}
|
||||
};
|
||||
let format = match get_video_format(mtype) {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
return MapResult {
|
||||
c: None,
|
||||
warnings: vec![format!("invalid video format. itag: {}", f.itag)],
|
||||
}
|
||||
let Some(format) = get_video_format(mtype) else {
|
||||
return MapResult {
|
||||
c: None,
|
||||
warnings: vec![format!("invalid video format. itag: {}", f.itag)],
|
||||
}
|
||||
};
|
||||
let map_res = map_url(&f.url, &f.signature_cipher, deobf, last_nsig);
|
||||
|
|
@ -532,9 +526,9 @@ fn map_video_stream(
|
|||
quality: f.quality_label.unwrap(),
|
||||
hdr: f.color_info.unwrap_or_default().primaries
|
||||
== player::Primaries::ColorPrimariesBt2020,
|
||||
mime: f.mime_type.to_owned(),
|
||||
format,
|
||||
codec: get_video_codec(codecs),
|
||||
mime: f.mime_type,
|
||||
throttled: url.throttled,
|
||||
}),
|
||||
warnings: map_res.warnings,
|
||||
|
|
@ -551,25 +545,19 @@ fn map_audio_stream(
|
|||
deobf: &Deobfuscator,
|
||||
last_nsig: &mut [String; 2],
|
||||
) -> MapResult<Option<AudioStream>> {
|
||||
let (mtype, codecs) = match parse_mime(&f.mime_type) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return MapResult {
|
||||
c: None,
|
||||
warnings: vec![format!(
|
||||
"Invalid mime type `{}` in video format {:?}",
|
||||
&f.mime_type, &f
|
||||
)],
|
||||
}
|
||||
let Some((mtype, codecs)) = parse_mime(&f.mime_type) else {
|
||||
return MapResult {
|
||||
c: None,
|
||||
warnings: vec![format!(
|
||||
"Invalid mime type `{}` in video format {:?}",
|
||||
&f.mime_type, &f
|
||||
)],
|
||||
}
|
||||
};
|
||||
let format = match get_audio_format(mtype) {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
return MapResult {
|
||||
c: None,
|
||||
warnings: vec![format!("invalid audio format. itag: {}", f.itag)],
|
||||
}
|
||||
let Some(format) = get_audio_format(mtype) else {
|
||||
return MapResult {
|
||||
c: None,
|
||||
warnings: vec![format!("invalid audio format. itag: {}", f.itag)],
|
||||
}
|
||||
};
|
||||
let map_res = map_url(&f.url, &f.signature_cipher, deobf, last_nsig);
|
||||
|
|
@ -586,9 +574,9 @@ fn map_audio_stream(
|
|||
index_range: f.index_range,
|
||||
init_range: f.init_range,
|
||||
duration_ms: f.approx_duration_ms,
|
||||
mime: f.mime_type.to_owned(),
|
||||
format,
|
||||
codec: get_audio_codec(codecs),
|
||||
mime: f.mime_type,
|
||||
channels: f.audio_channels,
|
||||
loudness_db: f.loudness_db,
|
||||
throttled: url.throttled,
|
||||
|
|
@ -686,7 +674,7 @@ fn map_audio_track(
|
|||
}
|
||||
},
|
||||
_ => {}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
AudioTrack {
|
||||
|
|
|
|||
|
|
@ -60,9 +60,8 @@ impl MapResponse<Playlist> for response::Playlist {
|
|||
lang: crate::param::Language,
|
||||
_deobf: Option<&crate::deobfuscate::DeobfData>,
|
||||
) -> Result<MapResult<Playlist>, ExtractionError> {
|
||||
let (contents, header) = match (self.contents, self.header) {
|
||||
(Some(contents), Some(header)) => (contents, header),
|
||||
_ => return Err(response::alerts_to_err(id, self.alerts)),
|
||||
let (Some(contents), Some(header)) = (self.contents, self.header) else {
|
||||
return Err(response::alerts_to_err(id, self.alerts));
|
||||
};
|
||||
|
||||
let video_items = contents
|
||||
|
|
|
|||
|
|
@ -87,11 +87,9 @@ impl From<ChannelRss> for crate::model::ChannelRss {
|
|||
feed.entry
|
||||
.iter()
|
||||
.find_map(|entry| {
|
||||
if !entry.channel_id.is_empty() {
|
||||
Some(entry.channel_id.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Some(entry.channel_id.as_str())
|
||||
.filter(|id| id.is_empty())
|
||||
.map(str::to_owned)
|
||||
})
|
||||
.or_else(|| {
|
||||
feed.author
|
||||
|
|
|
|||
|
|
@ -349,7 +349,7 @@ impl From<Icon> for crate::model::Verification {
|
|||
match icon.icon_type {
|
||||
IconType::Check => Self::Verified,
|
||||
IconType::OfficialArtistBadge => Self::Artist,
|
||||
_ => Self::None,
|
||||
IconType::Like => Self::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -500,7 +500,7 @@ impl MusicListMapper {
|
|||
|
||||
let pt_id = item
|
||||
.navigation_endpoint
|
||||
.and_then(|ne| ne.music_page())
|
||||
.and_then(NavigationEndpoint::music_page)
|
||||
.or_else(|| {
|
||||
c1.and_then(|c1| {
|
||||
c1.renderer.text.0.into_iter().next().and_then(|t| match t {
|
||||
|
|
@ -796,7 +796,7 @@ impl MusicListMapper {
|
|||
name: item.title,
|
||||
duration: None,
|
||||
cover: item.thumbnail_renderer.into(),
|
||||
artist_id: artists.first().and_then(|a| a.id.to_owned()),
|
||||
artist_id: artists.first().and_then(|a| a.id.clone()),
|
||||
artists,
|
||||
album: None,
|
||||
view_count: subtitle_p2.and_then(|c| {
|
||||
|
|
@ -872,7 +872,7 @@ impl MusicListMapper {
|
|||
id,
|
||||
name: item.title,
|
||||
cover: item.thumbnail_renderer.into(),
|
||||
artist_id: artists.first().and_then(|a| a.id.to_owned()),
|
||||
artist_id: artists.first().and_then(|a| a.id.clone()),
|
||||
artists,
|
||||
album_type,
|
||||
year,
|
||||
|
|
@ -886,8 +886,7 @@ impl MusicListMapper {
|
|||
let from_ytm = subtitle_p2
|
||||
.as_ref()
|
||||
.and_then(|p| p.0.first())
|
||||
.map(util::is_ytm)
|
||||
.unwrap_or(true);
|
||||
.map_or(true, util::is_ytm);
|
||||
let channel = subtitle_p2.and_then(|p| {
|
||||
p.0.into_iter().find_map(|c| ChannelId::try_from(c).ok())
|
||||
});
|
||||
|
|
@ -973,7 +972,7 @@ impl MusicListMapper {
|
|||
id,
|
||||
name: card.title,
|
||||
cover: card.thumbnail.into(),
|
||||
artist_id: artists.first().and_then(|a| a.id.to_owned()),
|
||||
artist_id: artists.first().and_then(|a| a.id.clone()),
|
||||
artists,
|
||||
album_type,
|
||||
year: subtitle_p3.and_then(|y| util::parse_numeric(y.first_str()).ok()),
|
||||
|
|
@ -1010,7 +1009,7 @@ impl MusicListMapper {
|
|||
name: card.title,
|
||||
duration,
|
||||
cover: card.thumbnail.into(),
|
||||
artist_id: artists.first().and_then(|a| a.id.to_owned()),
|
||||
artist_id: artists.first().and_then(|a| a.id.clone()),
|
||||
artists,
|
||||
album,
|
||||
view_count,
|
||||
|
|
@ -1024,8 +1023,7 @@ impl MusicListMapper {
|
|||
let from_ytm = subtitle_p2
|
||||
.as_ref()
|
||||
.and_then(|p| p.0.first())
|
||||
.map(util::is_ytm)
|
||||
.unwrap_or(true);
|
||||
.map_or(true, util::is_ytm);
|
||||
let channel = subtitle_p2
|
||||
.and_then(|p| p.0.into_iter().find_map(|c| ChannelId::try_from(c).ok()));
|
||||
let track_count =
|
||||
|
|
@ -1128,9 +1126,10 @@ impl MusicListMapper {
|
|||
///
|
||||
/// Therefore it is safest to discard such responses and retry the request.
|
||||
pub fn check_unknown(&self) -> Result<(), ExtractionError> {
|
||||
match self.has_unknown {
|
||||
true => Err(ExtractionError::InvalidData("unknown YTM items".into())),
|
||||
false => Ok(()),
|
||||
if self.has_unknown {
|
||||
Err(ExtractionError::InvalidData("unknown YTM items".into()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1167,7 +1166,7 @@ fn map_artist_id_fallback(
|
|||
fallback_artist: Option<&ArtistId>,
|
||||
) -> Option<String> {
|
||||
menu.and_then(|m| map_artist_id(m.menu_renderer.contents))
|
||||
.or_else(|| fallback_artist.and_then(|a| a.id.to_owned()))
|
||||
.or_else(|| fallback_artist.and_then(|a| a.id.clone()))
|
||||
}
|
||||
|
||||
pub(crate) fn map_artist_id(entries: Vec<MusicItemMenuEntry>) -> Option<String> {
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ impl<'de> Deserialize<'de> for BrowseEndpoint {
|
|||
let bep = BEp::deserialize(deserializer)?;
|
||||
|
||||
// Remove the VL prefix from the playlist id
|
||||
#[allow(clippy::map_unwrap_or)]
|
||||
let browse_id = bep
|
||||
.browse_endpoint_context_supported_configs
|
||||
.as_ref()
|
||||
|
|
@ -167,9 +168,8 @@ pub(crate) enum PageType {
|
|||
impl PageType {
|
||||
pub(crate) fn to_url_target(self, id: String) -> Option<UrlTarget> {
|
||||
match self {
|
||||
PageType::Artist => Some(UrlTarget::Channel { id }),
|
||||
PageType::Artist | PageType::Channel => Some(UrlTarget::Channel { id }),
|
||||
PageType::Album => Some(UrlTarget::Album { id }),
|
||||
PageType::Channel => Some(UrlTarget::Channel { id }),
|
||||
PageType::Playlist => Some(UrlTarget::Playlist { id }),
|
||||
PageType::Unknown => None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -419,8 +419,8 @@ impl<T> YouTubeListMapper<T> {
|
|||
Self {
|
||||
lang,
|
||||
channel: Some(ChannelTag {
|
||||
id: channel.id.to_owned(),
|
||||
name: channel.name.to_owned(),
|
||||
id: channel.id.clone(),
|
||||
name: channel.name.clone(),
|
||||
avatar: Vec::new(),
|
||||
verification: channel.verification,
|
||||
subscriber_count: channel.subscriber_count,
|
||||
|
|
@ -572,14 +572,15 @@ impl<T> YouTubeListMapper<T> {
|
|||
|
||||
fn map_channel(&mut self, channel: ChannelRenderer) -> ChannelItem {
|
||||
// channel handle instead of subscriber count (A/B test 3)
|
||||
let (sc_txt, vc_text) = match channel
|
||||
let (sc_txt, vc_text) = if channel
|
||||
.subscriber_count_text
|
||||
.as_ref()
|
||||
.map(|txt| txt.starts_with('@'))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
true => (channel.video_count_text, None),
|
||||
false => (channel.subscriber_count_text, channel.video_count_text),
|
||||
(channel.video_count_text, None)
|
||||
} else {
|
||||
(channel.subscriber_count_text, channel.video_count_text)
|
||||
};
|
||||
|
||||
ChannelItem {
|
||||
|
|
@ -643,7 +644,7 @@ impl YouTubeListMapper<YouTubeItem> {
|
|||
.map(|url| (l.title, util::sanitize_yt_url(&url.url)))
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
});
|
||||
}
|
||||
YouTubeListItem::RichItemRenderer { content } => {
|
||||
self.map_item(*content);
|
||||
|
|
@ -701,7 +702,7 @@ impl YouTubeListMapper<PlaylistItem> {
|
|||
match item {
|
||||
YouTubeListItem::PlaylistRenderer(playlist) => {
|
||||
let mapped = self.map_playlist(playlist);
|
||||
self.items.push(mapped)
|
||||
self.items.push(mapped);
|
||||
}
|
||||
YouTubeListItem::ContinuationItemRenderer {
|
||||
continuation_endpoint,
|
||||
|
|
|
|||
|
|
@ -168,12 +168,13 @@ impl RustyPipeQuery {
|
|||
e,
|
||||
Error::Extraction(ExtractionError::NotFound { .. })
|
||||
) {
|
||||
match util::VIDEO_ID_REGEX.is_match(id) {
|
||||
true => Ok(UrlTarget::Video {
|
||||
if util::VIDEO_ID_REGEX.is_match(id) {
|
||||
Ok(UrlTarget::Video {
|
||||
id: id.to_owned(),
|
||||
start_time: get_start_time(),
|
||||
}),
|
||||
false => Err(e),
|
||||
})
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
} else {
|
||||
Err(e)
|
||||
|
|
|
|||
|
|
@ -393,7 +393,7 @@ impl MapResponse<Paginator<Comment>> for response::VideoComments {
|
|||
lang,
|
||||
);
|
||||
comments.push(res.c);
|
||||
warnings.append(&mut res.warnings)
|
||||
warnings.append(&mut res.warnings);
|
||||
}
|
||||
response::video_details::CommentListItem::CommentRenderer(comment) => {
|
||||
let mut res = map_comment(
|
||||
|
|
@ -403,7 +403,7 @@ impl MapResponse<Paginator<Comment>> for response::VideoComments {
|
|||
lang,
|
||||
);
|
||||
comments.push(res.c);
|
||||
warnings.append(&mut res.warnings)
|
||||
warnings.append(&mut res.warnings);
|
||||
}
|
||||
response::video_details::CommentListItem::ContinuationItemRenderer {
|
||||
continuation_endpoint,
|
||||
|
|
@ -433,11 +433,11 @@ fn map_recommendations(
|
|||
let mut mapper = response::YouTubeListMapper::<VideoItem>::new(lang);
|
||||
mapper.map_response(r);
|
||||
|
||||
if let Some(continuations) = continuations {
|
||||
continuations.into_iter().for_each(|c| {
|
||||
mapper.ctoken = Some(c.next_continuation_data.continuation);
|
||||
})
|
||||
};
|
||||
mapper.ctoken = mapper.ctoken.or_else(|| {
|
||||
continuations
|
||||
.and_then(|c| c.into_iter().next())
|
||||
.map(|c| c.next_continuation_data.continuation)
|
||||
});
|
||||
|
||||
MapResult {
|
||||
c: Paginator::new_ext(
|
||||
|
|
|
|||
Reference in a new issue